public void ChainAnimation(string firstKey, string secondKey, float fadeTime) { CAAnimation firstAnim = animationsDict [firstKey]; CAAnimation secondAnim = animationsDict [secondKey]; if (firstAnim == null || secondAnim == null) { return; } SCNAnimationEventHandler chainEventBlock = (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { mainSkeleton.AddAnimation(secondAnim, secondKey); }; if (firstAnim.AnimationEvents == null || firstAnim.AnimationEvents.Length == 0) { firstAnim.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(fadeTime, chainEventBlock) }; } else { var pastEvents = new List <SCNAnimationEvent> (firstAnim.AnimationEvents); pastEvents.Add(SCNAnimationEvent.Create(fadeTime, chainEventBlock)); firstAnim.AnimationEvents = pastEvents.ToArray(); } }
private void SetAnimation(CharacterAnimation index, string animationName, string sceneName) { // Load the DAE using SCNSceneSource in order to be able to retrieve the animation by its identifier var sceneURL = NSUrl.FromFilename(NSBundle.MainBundle.PathForResource("Scenes.scnassets/boss/" + sceneName, "dae")); var sceneSource = SCNSceneSource.FromUrl(sceneURL, (NSDictionary)null); var bossAnimation = (CAAnimation)sceneSource.GetEntryWithIdentifier(animationName, new Class("CAAnimation")); Animations [(int)index] = bossAnimation; // Blend animations for smoother transitions bossAnimation.FadeInDuration = 0.3f; bossAnimation.FadeOutDuration = 0.3f; if (index == CharacterAnimation.Attack) { // Create an animation event and set it to the animation var attackSoundEvent = new SCNAnimationEventHandler((CAAnimation animation, NSObject animatedObject, bool playingBackward) => { InvokeOnMainThread(delegate { var soundUrl = NSUrl.FromFilename(NSBundle.MainBundle.PathForResource("Sounds/attack4", "wav")); new NSSound(soundUrl, false).Play(); }); }); bossAnimation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.4f, attackSoundEvent) }; } }
void SetupRunAnimation() { NSString runKey = KeyForAnimationType(CharacterAnimation.Run); NSString runStartKey = KeyForAnimationType(CharacterAnimation.RunStart); NSString runStopKey = KeyForAnimationType(CharacterAnimation.RunStop); CAAnimation runAnim = LoadAndCacheAnimation("art.scnassets/characters/explorer/run", runKey); CAAnimation runStartAnim = LoadAndCacheAnimation("art.scnassets/characters/explorer/run_start", runStartKey); CAAnimation runStopAnim = LoadAndCacheAnimation("art.scnassets/characters/explorer/run_stop", runStopKey); runAnim.RepeatCount = float.MaxValue; runStartAnim.RepeatCount = 0; runStopAnim.RepeatCount = 0; runAnim.FadeInDuration = 0.05f; runAnim.FadeOutDuration = 0.05f; runStartAnim.FadeInDuration = 0.05f; runStartAnim.FadeOutDuration = 0.05f; runStopAnim.FadeInDuration = 0.05f; runStopAnim.FadeOutDuration = 0.05f; SCNAnimationEventHandler stepLeftBlock = (animation, animatedObject, playingBackward) => { GameSimulation.Sim.PlaySound("leftstep.caf"); }; SCNAnimationEventHandler stepRightBlock = (animation, animatedObject, playingBackward) => { GameSimulation.Sim.PlaySound("rightstep.caf"); }; SCNAnimationEventHandler startWalkStateBlock = (animation, animatedObject, playingBackward) => { if (InRunAnimation) { IsWalking = true; } else { mainSkeleton.RemoveAnimation(runKey, 0.15f); } }; SCNAnimationEventHandler stopWalkStateBlock = (animation, animatedObject, playingBackward) => { IsWalking = false; TurnOffWalkingDust(); if (changingDirection) { inRunAnimation = false; InRunAnimation = true; changingDirection = false; playerWalkDirection = playerWalkDirection == WalkDirection.Left ? WalkDirection.Right : WalkDirection.Left; } }; runStopAnim.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(1f, stopWalkStateBlock) }; runAnim.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.0f, startWalkStateBlock), SCNAnimationEvent.Create(0.25f, stepRightBlock), SCNAnimationEvent.Create(0.75f, stepLeftBlock) }; }
void SetupJumpAnimation() { NSString jumpKey = KeyForAnimationType(CharacterAnimation.Jump); NSString fallingKey = KeyForAnimationType(CharacterAnimation.JumpFalling); NSString landKey = KeyForAnimationType(CharacterAnimation.JumpLand); NSString idleKey = KeyForAnimationType(CharacterAnimation.Idle); CAAnimation jumpAnimation = LoadAndCacheAnimation("art.scnassets/characters/explorer/jump_start", jumpKey); CAAnimation fallAnimation = LoadAndCacheAnimation("art.scnassets/characters/explorer/jump_falling", fallingKey); CAAnimation landAnimation = LoadAndCacheAnimation("art.scnassets/characters/explorer/jump_land", landKey); jumpAnimation.FadeInDuration = 0.15f; jumpAnimation.FadeOutDuration = 0.15f; fallAnimation.FadeInDuration = 0.15f; landAnimation.FadeInDuration = 0.15f; landAnimation.FadeOutDuration = 0.15f; jumpAnimation.RepeatCount = 0; fallAnimation.RepeatCount = 0; landAnimation.RepeatCount = 0; jumpForce = 7.0f; SCNAnimationEventHandler leaveGroundBlock = (animation, animatedObject, playingBackward) => { var jumpVelocity = new SCNVector3(0f, jumpForce * 2.1f, 0f); velocity = SCNVector3.Add(velocity, jumpVelocity); Launching = false; InJumpAnimation = false; }; SCNAnimationEventHandler pause = (animation, animatedObject, playingBackward) => { mainSkeleton.PauseAnimation(fallingKey); }; jumpAnimation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.25f, leaveGroundBlock) }; fallAnimation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.5f, pause) }; // Animation Sequence is to Jump -> Fall -> Land -> Idle. ChainAnimation(jumpKey, fallingKey); ChainAnimation(landKey, idleKey); }
void SetupTauntAnimation() { string path = GameSimulation.PathForArtResource("characters/monkey/monkey_tree_hang_taunt"); CAAnimation taunt = LoadAndCacheAnimation(path, "monkey_tree_hang_taunt-1"); taunt.RepeatCount = 0; SCNAnimationEventHandler ackBlock = (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { GameSimulation.Sim.PlaySound("ack.caf"); }; SCNAnimationEventHandler idleBlock = (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { isIdle = true; }; taunt.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.35f, ackBlock), SCNAnimationEvent.Create(1.0f, idleBlock) }; }
private void LoadAnimations() { var idleAnimation = Character.LoadAnimation("art.scnassets/character/max_idle.scn"); this.model.AddAnimation(idleAnimation, new NSString("idle")); idleAnimation.Play(); var walkAnimation = Character.LoadAnimation("art.scnassets/character/max_walk.scn"); walkAnimation.Speed = Character.SpeedFactor; walkAnimation.Stop(); if (Character.IsEnableFootStepSound) { walkAnimation.Animation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.1f, (animation, animatedObject, playingBackward) => { this.PlayFootStep(); }), SCNAnimationEvent.Create(0.6f, (animation, animatedObject, playingBackward) => { this.PlayFootStep(); }) }; } this.model.AddAnimation(walkAnimation, new NSString("walk")); var jumpAnimation = Character.LoadAnimation("art.scnassets/character/max_jump.scn"); jumpAnimation.Animation.RemovedOnCompletion = false; jumpAnimation.Stop(); jumpAnimation.Animation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0f, (animation, animatedObject, playingBackward) => { this.PlayJumpSound(); }) }; this.model.AddAnimation(jumpAnimation, new NSString("jump")); var spinAnimation = Character.LoadAnimation("art.scnassets/character/max_spin.scn"); spinAnimation.Animation.RemovedOnCompletion = false; spinAnimation.Speed = 1.5f; spinAnimation.Stop(); spinAnimation.Animation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0f, (animation, animatedObject, playingBackward) => { this.PlayAttackSound(); }) }; this.model.AddAnimation(spinAnimation, new NSString("spin")); }
void SetupGetCoconutAnimation() { SCNAnimationEventHandler pickupEventBlock = (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { if (coconutInHand != null) { coconutInHand.RemoveFromParentNode(); } coconutInHand = Coconut.CoconutProtoObject; rightHand.AddChildNode(coconutInHand); hasCoconut = true; }; CAAnimation getAnimation = LoadAndCacheAnimation(GameSimulation.PathForArtResource("characters/monkey/monkey_get_coconut"), "monkey_get_coconut-1"); if (getAnimation.AnimationEvents == null) { getAnimation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.4f, pickupEventBlock) } } ; getAnimation.RepeatCount = 1; } void SetupThrowAnimation() { CAAnimation throwAnimation = LoadAndCacheAnimation(GameSimulation.PathForArtResource("characters/monkey/monkey_throw_coconut"), "monkey_throw_coconut-1"); throwAnimation.Speed = 1.5f; if (throwAnimation.AnimationEvents == null || throwAnimation.AnimationEvents.Length == 0) { SCNAnimationEventHandler throwEventBlock = ThrowCoconut; throwAnimation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.35f, throwEventBlock) }; } throwAnimation.RepeatCount = 0; } void ThrowCoconut(CAAnimation animation, NSObject animatedObject, bool playingBackward) { if (!hasCoconut) { return; } SCNMatrix4 worldMtx = coconutInHand.PresentationNode.WorldTransform; coconutInHand.RemoveFromParentNode(); Coconut node = Coconut.CoconutThrowProtoObject; SCNPhysicsShape coconutPhysicsShape = Coconut.CoconutPhysicsShape; node.PhysicsBody = SCNPhysicsBody.CreateBody(SCNPhysicsBodyType.Dynamic, coconutPhysicsShape); node.PhysicsBody.Restitution = 0.9f; node.PhysicsBody.CollisionBitMask = GameCollisionCategory.Player | GameCollisionCategory.Ground; node.PhysicsBody.CategoryBitMask = GameCollisionCategory.Coconut; node.Transform = worldMtx; GameSimulation.Sim.RootNode.AddChildNode(node); GameSimulation.Sim.GameLevel.Coconuts.Add(node); node.PhysicsBody.ApplyForce(new SCNVector3(-200, 500, 300), true); hasCoconut = false; isIdle = true; } }
public override void SetupSlide(PresentationViewController presentationViewController) { TextManager.SetTitle("Constraints"); TextManager.SetSubtitle("SCNIKConstraint"); TextManager.AddBulletAtLevel("Inverse Kinematics", 0); TextManager.AddBulletAtLevel("Node chain", 0); TextManager.AddBulletAtLevel("Target", 0); //load the hero Hero = Utils.SCAddChildNode(GroundNode, "heroGroup", "Scenes.scnassets/hero/hero", 12); Hero.Position = new SCNVector3(0, 0, 5); Hero.Rotation = new SCNVector4(1, 0, 0, -(nfloat)Math.PI / 2); //hide the sword var sword = Hero.FindChildNode("sword", true); sword.Hidden = true; //load attack animation var path = NSBundle.MainBundle.PathForResource("Scenes.scnassets/hero/attack", "dae"); var source = SCNSceneSource.FromUrl(NSUrl.FromFilename(path), (NSDictionary)null); Attack = (CAAnimation)source.GetEntryWithIdentifier("attackID", new Class("CAAnimation")); Attack.RepeatCount = 0; Attack.FadeInDuration = 0.1f; Attack.FadeOutDuration = 0.3f; Attack.Speed = 0.75f; Attack.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.55f, (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { if (IkActive) { DestroyTarget(); } }) }; AnimationDuration = Attack.Duration; //setup IK var hand = Hero.FindChildNode("Bip01_R_Hand", true); var clavicle = Hero.FindChildNode("Bip01_R_Clavicle", true); var head = Hero.FindChildNode("Bip01_Head", true); Ik = SCNIKConstraint.Create(clavicle); hand.Constraints = new SCNConstraint[] { Ik }; Ik.InfluenceFactor = 0.0f; //add target Target = SCNNode.Create(); Target.Position = new SCNVector3(-4, 7, 10); Target.Opacity = 0; Target.Geometry = SCNPlane.Create(2, 2); Target.Geometry.FirstMaterial.Diffuse.Contents = new NSImage(NSBundle.MainBundle.PathForResource("Images/target", "png")); GroundNode.AddChildNode(Target); //look at LookAt = SCNLookAtConstraint.Create(Target); LookAt.InfluenceFactor = 0; head.Constraints = new SCNConstraint[] { LookAt }; ((SCNView)presentationViewController.View).WeakSceneRendererDelegate = this; }
private void SetAnimation(CharacterAnimation index, string animationName, string sceneName) { // Load the DAE using SCNSceneSource in order to be able to retrieve the animation by its identifier var path = NSBundle.MainBundle.PathForResource("Scenes/hero/" + sceneName, "dae"); var sceneURL = NSUrl.FromFilename(path); var sceneSource = SCNSceneSource.FromUrl(sceneURL, (NSDictionary)null); var animation = (CAAnimation)sceneSource.GetEntryWithIdentifier(animationName, new Class("CAAnimation")); Animations [(int)index] = animation; // Blend animations for smoother transitions animation.FadeInDuration = 0.3f; animation.FadeOutDuration = 0.3f; if (index == CharacterAnimation.Die) { // We want the "death" animation to remain at its final state at the end of the animation animation.RemovedOnCompletion = false; animation.FillMode = CAFillMode.Both; // Create animation events and set them to the animation var swipeSoundEventHandler = new SCNAnimationEventHandler((CAAnimation handlerAnimation, NSObject animatedObject, bool playingBackward) => { InvokeOnMainThread(delegate { var soundUrl = NSUrl.FromFilename(NSBundle.MainBundle.PathForResource("Sounds/swipe", "wav")); new NSSound(soundUrl, false).Play(); }); }); var deathSoundEventBlock = new SCNAnimationEventHandler((CAAnimation handlerAnimation, NSObject animatedObject, bool playingBackward) => { InvokeOnMainThread(delegate { var soundUrl = NSUrl.FromFilename(NSBundle.MainBundle.PathForResource("Sounds/death", "wav")); new NSSound(soundUrl, false).Play(); }); }); animation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.0f, swipeSoundEventHandler), SCNAnimationEvent.Create(0.3f, deathSoundEventBlock) }; } if (index == CharacterAnimation.Attack) { // Create an animation event and set it to the animation var swordSoundEventHandler = new SCNAnimationEventHandler((CAAnimation handlerAnimation, NSObject animatedObject, bool playingBackward) => { InvokeOnMainThread(delegate { var soundUrl = NSUrl.FromFilename(NSBundle.MainBundle.PathForResource("Sounds/sword", "wav")); var attackSound = new NSSound(soundUrl, false); attackSound.Play(); }); }); animation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.4f, swordSoundEventHandler) }; } if (index == CharacterAnimation.Walk) { // Repeat the walk animation 3 times animation.RepeatCount = 3; // Create an animation event and set it to the animation var stepSoundEventHandler = new SCNAnimationEventHandler((CAAnimation handlerAnimation, NSObject animatedObject, bool playingBackward) => { InvokeOnMainThread(delegate { var soundUrl = NSUrl.FromFilename(NSBundle.MainBundle.PathForResource("Sounds/walk", "wav")); new NSSound(soundUrl, false).Play(); }); }); animation.AnimationEvents = new SCNAnimationEvent[] { SCNAnimationEvent.Create(0.2f, stepSoundEventHandler), SCNAnimationEvent.Create(0.7f, stepSoundEventHandler) }; } }
public Character() { for (int i = 0; i < StepsSoundCount; i++) { steps [i, (int)FloorMaterial.Grass] = SCNAudioSource.FromFile(string.Format("game.scnassets/sounds/Step_grass_0{0}.mp3", i)); steps [i, (int)FloorMaterial.Grass].Volume = 0.5f; steps [i, (int)FloorMaterial.Rock] = SCNAudioSource.FromFile(string.Format("game.scnassets/sounds/Step_rock_0{0}.mp3", i)); if (i < StepsInWaterSoundCount) { steps [i, (int)FloorMaterial.Water] = SCNAudioSource.FromFile(string.Format("game.scnassets/sounds/Step_splash_0{0}.mp3", i)); steps [i, (int)FloorMaterial.Water].Load(); } else { steps [i, (int)FloorMaterial.Water] = steps [i % StepsInWaterSoundCount, (int)FloorMaterial.Water]; } steps [i, (int)FloorMaterial.Rock].Load(); steps [i, (int)FloorMaterial.Grass].Load(); // Load the character. SCNScene characterScene = SCNScene.FromFile("game.scnassets/panda.scn"); SCNNode characterTopLevelNode = characterScene.RootNode.ChildNodes [0]; Node = SCNNode.Create(); Node.AddChildNode(characterTopLevelNode); // Configure the "idle" animation to repeat forever foreach (var childNode in characterTopLevelNode.ChildNodes) { foreach (var key in childNode.GetAnimationKeys()) { CAAnimation animation = childNode.GetAnimation(key); animation.UsesSceneTimeBase = false; animation.RepeatCount = float.PositiveInfinity; childNode.AddAnimation(animation, key); } } // retrieve some particle systems and save their birth rate fireEmitter = characterTopLevelNode.FindChildNode("fire", true); fireBirthRate = fireEmitter.ParticleSystems [0].BirthRate; fireEmitter.ParticleSystems [0].BirthRate = 0; fireEmitter.Hidden = false; smokeEmitter = characterTopLevelNode.FindChildNode("smoke", true); smokeBirthRate = smokeEmitter.ParticleSystems [0].BirthRate; smokeEmitter.ParticleSystems [0].BirthRate = 0; smokeEmitter.Hidden = false; whiteSmokeEmitter = characterTopLevelNode.FindChildNode("whiteSmoke", true); whiteSmokeBirthRate = whiteSmokeEmitter.ParticleSystems [0].BirthRate; whiteSmokeEmitter.ParticleSystems [0].BirthRate = 0; whiteSmokeEmitter.Hidden = false; SCNVector3 min = SCNVector3.Zero; SCNVector3 max = SCNVector3.Zero; Node.GetBoundingBox(ref min, ref max); float radius = (max.X - min.X) * .4f; float height = (max.Y - min.Y); // Create a kinematic with capsule. SCNNode colliderNode = SCNNode.Create(); colliderNode.Name = "collider"; colliderNode.Position = new SCNVector3(0f, height * .51f, 0f); colliderNode.PhysicsBody = SCNPhysicsBody.CreateBody( SCNPhysicsBodyType.Kinematic, SCNPhysicsShape.Create(SCNCapsule.Create(radius, height)) ); // We want contact notifications with the collectables, enemies and walls. colliderNode.PhysicsBody.ContactTestBitMask = (nuint)(int)(Bitmask.SuperCollectable | Bitmask.Collectable | Bitmask.Collision | Bitmask.Enemy); Node.AddChildNode(colliderNode); walkAnimation = LoadAnimationFromSceneNamed("game.scnassets/walk.scn"); walkAnimation.UsesSceneTimeBase = false; walkAnimation.FadeInDuration = .3f; walkAnimation.FadeOutDuration = .3f; walkAnimation.RepeatCount = float.PositiveInfinity; walkAnimation.Speed = CharacterSpeedFactor; // Play foot steps at specific times in the animation walkAnimation.AnimationEvents = new [] { SCNAnimationEvent.Create(.1f, (animation, animatedObject, playingBackward) => PlayFootStep()), SCNAnimationEvent.Create(.6f, (animation, animatedObject, playingBackward) => PlayFootStep()) }; } }
public override void PresentStep(int index, PresentationViewController presentationViewController) { // Animate by default SCNTransaction.Begin(); SCNTransaction.AnimationDuration = 0; switch (index) { case 0: TextManager.FlipInText(SlideTextManager.TextType.Bullet); break; case 1: TextManager.FlipOutText(SlideTextManager.TextType.Bullet); TextManager.AddEmptyLine(); TextManager.AddCode("#// Rotate forever\n" + "[aNode #runAction:#\n" + " [SCNAction repeatActionForever:\n" + " [SCNAction rotateByX:0 y:M_PI*2 z:0 duration:5.0]]];#"); TextManager.FlipInText(SlideTextManager.TextType.Code); break; case 2: TextManager.FlipOutText(SlideTextManager.TextType.Bullet); TextManager.FlipOutText(SlideTextManager.TextType.Code); TextManager.AddBulletAtLevel("Move", 0); TextManager.AddBulletAtLevel("Rotate", 0); TextManager.AddBulletAtLevel("Scale", 0); TextManager.AddBulletAtLevel("Opacity", 0); TextManager.AddBulletAtLevel("Remove", 0); TextManager.AddBulletAtLevel("Wait", 0); TextManager.AddBulletAtLevel("Custom block", 0); TextManager.FlipInText(SlideTextManager.TextType.Bullet); break; case 3: TextManager.FlipOutText(SlideTextManager.TextType.Bullet); TextManager.AddEmptyLine(); TextManager.AddBulletAtLevel("Directly targets the render tree", 0); TextManager.FlipInText(SlideTextManager.TextType.Bullet); break; case 4: TextManager.AddBulletAtLevel("node.position / node.presentationNode.position", 0); //labels var label1 = TextManager.AddTextAtLevel("Action", 0); label1.Position = new SCNVector3(-15, 3, 0); var label2 = TextManager.AddTextAtLevel("Animation", 0); label2.Position = new SCNVector3(-15, -2, 0); //animation var animNode = SCNNode.Create(); var cubeSize = 4.0f; animNode.Position = new SCNVector3(-5, cubeSize / 2, 0); var cube = SCNBox.Create(cubeSize, cubeSize, cubeSize, 0.05f * cubeSize); cube.FirstMaterial.Diffuse.Contents = new NSImage(NSBundle.MainBundle.PathForResource("SharedTextures/texture", "png")); cube.FirstMaterial.Diffuse.MipFilter = SCNFilterMode.Linear; cube.FirstMaterial.Diffuse.WrapS = SCNWrapMode.Repeat; cube.FirstMaterial.Diffuse.WrapT = SCNWrapMode.Repeat; animNode.Geometry = cube; ContentNode.AddChildNode(animNode); SCNTransaction.Begin(); SCNNode animPosIndicator = null; SCNAnimationEvent startEvt = SCNAnimationEvent.Create(0, (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { SCNTransaction.Begin(); SCNTransaction.AnimationDuration = 0; animPosIndicator.Position = new SCNVector3(10, animPosIndicator.Position.Y, animPosIndicator.Position.Z); SCNTransaction.Commit(); }); SCNAnimationEvent endEvt = SCNAnimationEvent.Create(1, (CAAnimation animation, NSObject animatedObject, bool playingBackward) => { SCNTransaction.Begin(); SCNTransaction.AnimationDuration = 0; animPosIndicator.Position = new SCNVector3(-5, animPosIndicator.Position.Y, animPosIndicator.Position.Z); SCNTransaction.Commit(); }); var anim = CABasicAnimation.FromKeyPath("position.x"); anim.Duration = 3; anim.From = new NSNumber(0.0); anim.To = new NSNumber(15.0); anim.Additive = true; anim.AutoReverses = true; anim.AnimationEvents = new SCNAnimationEvent[] { startEvt, endEvt }; anim.RepeatCount = float.MaxValue; animNode.AddAnimation(anim, new NSString("cubeAnimation")); //action var actionNode = SCNNode.Create(); actionNode.Position = new SCNVector3(-5, cubeSize * 1.5f + 1, 0); actionNode.Geometry = cube; ContentNode.AddChildNode(actionNode); var mv = SCNAction.MoveBy(15, 0, 0, 3); actionNode.RunAction(SCNAction.RepeatActionForever(SCNAction.Sequence(new SCNAction[] { mv, mv.ReversedAction() }))); //position indicator var positionIndicator = SCNNode.Create(); positionIndicator.Geometry = SCNCylinder.Create(0.5f, 0.01f); positionIndicator.Geometry.FirstMaterial.Diffuse.Contents = NSColor.Red; positionIndicator.Geometry.FirstMaterial.LightingModelName = SCNLightingModel.Constant; positionIndicator.EulerAngles = new SCNVector3(NMath.PI / 2, 0, 0); positionIndicator.Position = new SCNVector3(0, 0, cubeSize * 0.5f); actionNode.AddChildNode(positionIndicator); //anim pos indicator animPosIndicator = positionIndicator.Clone(); animPosIndicator.Position = new SCNVector3(5, cubeSize / 2, cubeSize * 0.5f); ContentNode.AddChildNode(animPosIndicator); SCNTransaction.Commit(); break; } SCNTransaction.Commit(); }