SKSpriteNode with physicsBody auto correct orientation - swift

What would be the best way to make a Swift SKSpriteNode auto correct its orientation after it has had a couple of physicsBody knocks and is now not in the default orientation?
For example I have have a 2D stick-man who gets knocked to the side by another object and would like the stick-man to tilt and reorient itself to the default standing position.

The way I have done this in the past is to wait until the physics body comes to rest, turn off the physics engine for the stickman, reorient the stickman, turn the physics engine back on again.
Write yourself an action called standupAction which animates the stickman's reorientation, maybe just a rotate or a jump up. Then do something like the following.
Sorry this Objective-C not Swift, don't know swift, but should be easy to translate.
if (stickman.physicsBody.resting) {
stickman.physicsBody.dynamic = NO;
[stickman runAction:[SKAction sequence:#[
standupAction,
[SKAction runBlock:^{ stickman.physicsBody.dynamic = YES;}]
]]];
}
The only downside of this approach is that sometimes it can take a full second until the stickman comes comes to rest. Box2d takes quite a long time to settle down. But it looks very realistic if you wait.
Alternatively, you could just check the orientation of stickman and if he is >45degrees off balance then reorient him with impulses. I find impulses to be very difficult to know how much force to apply when you need precise movement like this.

Just to update, I couldn't afford to set the physicsBody to not be dynamic.
My scene updates each node, similarly to Apple's Adventure game, as such I implemented the rotation in the subclassed SKSpriteNode's update method as such:
if (isTouchingGround) {
if self.zRotation != 0 && !rotating {
let actions = [
SKAction.runBlock {
self.rotating = true
},
SKAction.rotateToAngle(0, duration: 1.0),
SKAction.runBlock {
self.rotating = false
}]
runAction(SKAction.sequence(actions))
}
}

Related

Changing an object's position in Update() prevents the animation from playing

I'm making a turn-based grid-based game with a top-down perspective. Enemies move when the player moves, like a traditional roguelike.
Each character in the game (including the hero and enemies) has a futurePosition attribute that determines where they're supposed to move next. When the player presses a direction key, the hero's futurePosition is set to the direction indicated by the key, then every enemies' futurePosition is updated as well. The code in the Update() function of those characters looks like this :
public virtual void Update() {
if (MustMoveObject()) {
transform.position = Vector3.MoveTowards(transform.position, futurePosition, inverseMoveTime * Time.deltaTime);
} else if (attacking) {
Attack();
futurePosition = Vector3Int.RoundToInt(futurePosition);
}
}
Note: MustMoveObject() returns true if futurePosition and transform.position are not equal
If the game detects there's an enemy in the direction the player is going towards, it will attack it instead. The code looks like this:
MovingObject enemyToAttack = GameManager.instance.CheckForCreatureCurrentPosition(pos);
if (enemyToAttack != null)
{
target = enemyToAttack;
cameraFollow=false;
attacking=true;
animator.SetTrigger("attack");
futurePosition = Vector3.Lerp(transform.position, pos, 0.2f);
} else {
target=null;
cameraFollow=true;
futurePosition = pos;
}
As you can see in the code above, when attacking an enemy, the futurePosition attribute is set at about one fifth (or 20%) of the distance between the hero and the enemy, and "attacking" is set to true. When the player reaches that point in the Update() function, it attacks the enemy and then goes back to its original position. I hope this is clear so far.
Here's my problem : I recently added a test animation for the hero when it attacks, but the animation doesn't start when it should at animator.SetTrigger("attack");. It only activates after the movements of both the hero and the enemies is over. I know that this is the problem because if I replace the line transform.position = Vector3.MoveTowards(transform.position, futurePosition, inverseMoveTime * Time.deltaTime); with transform.position = futurePosition;, it makes the deplacement immediate and the animation triggers instantly. I want the animation to occur while the object is moving towards futurePosition.
Here's what the transition looks like in the inspector (I start from AnyState instead of IdleRight because I just wanted to test things out) :
Screenshot
As you can see, there's no exit time. There's also no delay in the animation itself, I've checked. Everything else is working as expected.
I'm also using Unity version 2019.4.37f1, I'm not sure whether that's relevant.
I hope I made my problem clear and that someone has an idea of what to do. I've searched for a solution, but it didn't seem like anyone had the same issue. If anything, it seemed most people had the opposite problem, where the object wouldn't move as long as the animation was active...
Thanks in advance for your help.
In your animation settings you can toggle loop and the box right under it, also add arguments when you're calling play like that: anim.play("thisAnim", 0, 0.25f) this way it will trigger the animation as soon as you call it, and not after your current anim is finished
Well, I figured it out... It was very dumb.
I needed to check "Has Exit Time" on for the transition between "Any State" and the various Idle States in the inspector. That was literally just it. :/

Swift: SKConstraint for scale? (or equivalent) Stuttering

At the minute I'm using the SKConstraint.positionX(rangex, y: rangey) to confine my SKCameraNode within the game board I've created. This is nice because when you hit the boundary there's no stuttering. But my current way to cap the scale of the camera creates a stutter as it hits the bound as it goes past and pings back.
#objc func zoomedView(_ sender:UIPinchGestureRecognizer) {
if newCamera.xScale > 0.148{
let pinch = SKAction.scale(by: 1/sender.scale, duration: 0.0)
newCamera.run(pinch)
sender.scale = 1.0
} else {newCamera.setScale(0.148)}
}
Is there an SKConstraint for scale (or equivalent) which is a better way to stop this stutter? Thanks :)
There is no direct SKConstraint equivalent for scale, however the reason you're experiencing the stuttering is as you go over the bound it snaps back the next time the function is called, rather before a frame is rendered, so theoretically you could zoom in massively instantaneously, and stay there until you activate the zoom function again.
A way to create an equivalent is to put the code checking whether the scale is greater than x in the rendering loop as outlined here.
So if you were to check at the last possible moment:
override func didFinishUpdate() {
if newCamera.xScale < 0.148{
newCamera.setScale(0.148)
} else if newCamera.xScale > 10{
newCamera.setScale(10)
}
}

How do run an SKAction on a group of SKSpriteNodes with the same name?

I'm making a game in Xcode 6 using spriteKit and swift. I have a plane on scene, and to make it look like its moving, I'm create clouds off the scene to the left of the screen. I then us an SKAction to move the cloud to the right side of the screen. This works great. You then click to jump off the plane, and the plane moves up off the scene. I then have it start making the clouds on the bottom of the scene, then they move up off the top of the scene, but the problem is, the already existing clouds still have to move to the right side of the screen. My question is, how do I make it so all of the existing clouds stop their action that moves them to the right, then begin to move up exactly where they are? How do I access the group of existing clouds all at the same time once they have been created? I also want the clouds to slow down after you have jumped when you tap the screen to open your parachute, but this should be able to be done by ending the SKAction that moves the clouds, then using another SKAction on them that moves them up slower, but I don't know how to access a group of SKSpriteNodes.
Here is the code that I have to make the clouds:
//This is how the cloud is first declared at the top of the .swift file
var cloud = SKSpriteNode()
//This is the function that runs every certain interval through an NSTimer
func createCloud()
{
cloud = SKSpriteNode(imageNamed: "cloud")
cloud.xScale = 1.25
cloud.yScale = cloud.xScale/2
cloud.zPosition = -1
self.addChild(cloud)
if personDidJump == false
{
let moveRight = SKAction.moveToX(CGRectGetWidth(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(-CGRectGetWidth(cloud.frame), randomNumber)
cloud.runAction(moveRight)
}
if personDidJump == true
{
let moveUp = SKAction.moveToY(CGRectGetHeight(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(randomNumber, -CGRectGetHeight(cloud.frame))
cloud.runAction(moveUp)
}
}
Also, should I be worried about deleting the clouds when they move off the scene? Or can I just leave them there because you can't see them, and from what I've seen, they don't lag you.
Any help would be greatly appreciated. Thank you.
-Callum-
Put your clouds in an array. When your player jumps (or whenever you need to move them) run through the array and run your action on each cloud.
stackoverflow.com/a/24213529/3402095
^ That is where I found my answere ^
When you make a node give it a name:
myNode.name = "A Node Name"
self.addChild(myNode)
If there are a lot of these nodes, later you can change properties or preform SKAcions on these nodes or do whatever you want to do by using enumerateChildNodesWithName:
self.enumerateChildNodesWithName("A Node Name")
{
myNode, stop in
myNode.runAction(SKAction.moveToY(CGRectGetHeight(self.Frame), duration: 1)
}
I hope this is useful to whoever may need it.

Unity3D OffNavMesh jump issue

I have set up Unity navigation meshes (four planes), navigation agent (sphere) and set up automatic and manual off mesh links. It should now jump between meshes. It does jump between meshes, but it does that in straight lines.
In other words, when agent comes to an edge, instead of actually jumping up (like off mesh link is drawn) it just moves straight in line but a bit faster. I tried moving one plane higher than others, but sphere still was jumping in straight line.
Is it supposed to be like this? Is it possible to set up navigation to jump by some curve? Or should I try to implement that myself?
I came by this question, and had to dig through the Unity sample. I just hope to make it easier for people by extracting the important bits.
To apply your own animation/transition across a navmesh link, you need to tell Unity that you will handle all offmesh link traversal, then add code that regularly checks to see if the agent is on an offmesh link. Finally, when the transition is complete, you need to tell Unity you've moved the agent, and resume normal navmesh behaviour.
The way you handle link logic is up to you. You can just go in a straight line, have a spinning wormhole, whatever. For jump, unity traverses the link using animation progress as the lerp argument, this works pretty nicely. (if you're doing looping or more complex animations, this doesn't work so well)
The important unity bits are:
_navAgent.autoTraverseOffMeshLink = false; //in Start()
_navAgent.currentOffMeshLinkData; //the link data - this contains start and end points, etc
_navAgent.CompleteOffMeshLink(); //Tell unity we have traversed the link (do this when you've moved the transform to the end point)
_navAgent.Resume(); //Resume normal navmesh behaviour
Now a simple jump sample...
using UnityEngine;
[RequireComponent(typeof(NavMeshAgent))]
public class NavMeshAnimator : MonoBehaviour
{
private NavMeshAgent _navAgent;
private bool _traversingLink;
private OffMeshLinkData _currLink;
void Start()
{
// Cache the nav agent and tell unity we will handle link traversal
_navAgent = GetComponent<NavMeshAgent>();
_navAgent.autoTraverseOffMeshLink = false;
}
void Update()
{
//don't do anything if the navagent is disabled
if (!_navAgent.enabled) return;
if (_navAgent.isOnOffMeshLink)
{
if (!_traversingLink)
{
//This is done only once. The animation's progress will determine link traversal.
animation.CrossFade("Jump", 0.1f, PlayMode.StopAll);
//cache current link
_currLink = _navAgent.currentOffMeshLinkData;
//start traversing
_traversingLink = true;
}
//lerp from link start to link end in time to animation
var tlerp = animation["Jump"].normalizedTime;
//straight line from startlink to endlink
var newPos = Vector3.Lerp(_currLink.startPos, _currLink.endPos, tlerp);
//add the 'hop'
newPos.y += 2f * Mathf.Sin(Mathf.PI * tlerp);
//Update transform position
transform.position = newPos;
// when the animation is stopped, we've reached the other side. Don't use looping animations with this control setup
if (!animation.isPlaying)
{
//make sure the player is right on the end link
transform.position = _currLink.endPos;
//internal logic reset
_traversingLink = false;
//Tell unity we have traversed the link
_navAgent.CompleteOffMeshLink();
//Resume normal navmesh behaviour
_navAgent.Resume();
}
}
else
{
//...update walk/idle animations appropriately ...etc
Its recommended to solve your problems via animation. Just create a Jump animation for your object, and play it at the correct time.
The position is relative, so if you increase the Y-position in your animation it will look like the object is jumping.
This is also how the Unity sample is working, with the soldiers running around.
Not sure what version of unity you are using but you could also try this, I know it works just fine in 4:
string linkType = GetComponent<NavMeshAgent>().currentOffMeshLinkData.linkType.ToString();
if(linkType == "LinkTypeJumpAcross"){
Debug.Log ("Yeah im in the jump already ;)");
}
also just some extra bumf for you, its best to use a proxy and follow the a navAgent game object:
Something like:
AIMan = this.transform.position;
AI_Proxy.transform.position = AIMan;
And also be sure to use:
AI_Proxy.animation["ProxyJump"].blendMode = AnimationBlendMode.Additive;
If you are using the in built unity animation!
K, that's my good deed for this week.
Fix position in update()
if (agent.isOnOffMeshLink)
{
transform.position = new Vector3(transform.position.x, 0f, transform.position.z);
}

Cocos2D getting progress of CCAction

I have a Cocos2D game with Box2D physics. In my GameScene.mm, I'm working on a method to zoom to a given scale:
-(void) zoomToScale:(float)zoom withDuration:(ccTime)duration
{
id action = [CCScaleTo actionWithDuration:duration scale:zoom];
[scrollNode runAction:action];
currentZoomLevel = zoom;
}
The problem that I'm having is that currentZoomLevel (which is used in the Scene's update() method) is set to the zoom immediately, and isn't gradually adjusted as per the animation. So while the animation is in progress, the currentZoomLevel variable is totally wrong.
I'm trying to figure out a way to have the currentZoomLevel variable match the progress of the animation as it's happening. According to the CCAction API Reference, the CCAction's update method takes a ccTime that's between 0 and 1 based on the progress of the animation (0 is just started, 1 is just finished).
How can I access this ccTime from outside of the action? I want to have something in my Scene's update method like this:
if(animating)
{
float progress = [action getProgress]; // How do I do this?
// Do math to update currentZoomLevel based on progress
}
Am I missing something obvious here, or am I going to have to subclass CCScaleTo?
You should be able to access the scale directly as it animates.
instead of
float progress = [action getProgress];
try
float current_scale = some_node.scale ;
where "some_node" is the thing you're animating/scaling.
Actually, your best bet is to use the new Cocos2D extension "CCLayerPanZoom", which handles all of this marvellously for you! It should be part of any new cocos2D install (v.1.0+).