I have a button (decrease), and when the button is pressed an arrow rotates left. I need the arrow to stop at a certain angle (approximately at 8 o'clock).
How can I get the angle of the arrow, and then stop it from rotating past this point (even if the user keeps pressing the button)?
Here's my code:
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
stop();
var rotate = 0;
decrease.addEventListener(MouseEvent.MOUSE_DOWN, decreasePressed);
decrease.addEventListener(MouseEvent.MOUSE_UP, removeEnterFrame);
function decreasePressed(e:MouseEvent):void
{
rotate = -2;
addEnterFrame();
}
function addEnterFrame():void
{
this.addEventListener(Event.ENTER_FRAME, update);
}
function removeEnterFrame(e:MouseEvent):void
{
this.removeEventListener(Event.ENTER_FRAME, update);
}
function update(e:Event):void
{
arrow1.rotation += rotate;
}
Assuming your arrow has been created vertically with the arrow pointing upwards, the 8 o'clock position when rotating anti-clockwise (as you are doing) is at approximately -112 degrees. Therefore you need to add a check in your update method to ensure that the arrow's rotation is not set to a value lower than -112:
// May need to adjust this depending on how your arrow is created
var minDegrees = -112;
function update(e:Event):void
{
var position:int = arrow1.rotation + rotate;
// Check increment of rotation does not take us past our limit
if (position <= minDegrees)
position = minDegrees;
arrow1.rotation = position;
}
Related
This question and others discuss how to track a node in SpriteKit using a SKCameraNode.
However, our needs vary.
Other solutions, such as updating the camera's position in update(_ currentTime: CFTimeInterval) of the SKScene, do not work because we only want to adjust the camera position after the node has moved Y pixels down the screen.
In other words, if the node moves 10 pixels up, the camera should remain still. If the node moves left or right, the camera should remain still.
We tried animating the camera's position over time instead of instantly, but running a SKAction against the camera inside of update(_ currentTime: CFTimeInterval) fails to do anything.
I just quickly made this. I believe this is what you are looking for?
(the actual animation is smooth, just i had to compress the GIF)
This is update Code:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
SKShapeNode *ball = (SKShapeNode*)[self childNodeWithName:#"ball"];
if (ball.position.y>100) camera.position = ball.position;
if (fabs(ball.position.x-newLoc.x)>10) {
// move x
ball.position = CGPointMake(ball.position.x+stepX, ball.position.y);
}
if (fabs(ball.position.y-newLoc.y)>10) {
// move y
ball.position = CGPointMake(ball.position.x, ball.position.y+stepY);
}
}
I would not put this in the update code, try to keep your update section clutter free, remember you only have 16ms to work with.
Instead create a sub class for your character node, and override the position property. What we are basically saying is if your camera is 10 pixels away from your character, move towards your character. We use a key on our action so that we do not get multiple actions stacking up and a timing mode to allow for the camera to smoothly move to your point, instead of being instant.
class MyCharacter : SKSpriteNode
{
override var position : CGPoint
{
didSet
{
if let scene = self.scene, let camera = scene.camera,(abs(position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
}
}
Edit: #Epsilon reminded me that SKActions and SKPhysics access the variable directly instead of going through the stored property, so this will not work. In this case, do it at the didFinishUpdate method:
override func didFinishUpdate()
{
//character should be a known property to the class, calling find everytime is too slow
if let character = self.character, let camera = self.camera,(abs(character.position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: character.position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
Im using a technique to control a sprite by rotating left/right and then accelerating forward. I have 2 questions regarding it. (The code it pasted together from different classes due to polymorphism. If it doesn't make sense, let me know. The movement works well and the off screen detection as well.)
When player moves off screen i call the Bounce method. I want the player not to be able to move off screen but to change direction and go back. This works on top and bottom but left and right edge very seldom. Mostly it does a wierd bounce and leaves the screen.
I would like to modify the accelerate algorithm so that i can set a max speed AND a acceleration speed. Atm the TangentalVelocity does both.
float TangentalVelocity = 8f;
//Called when up arrow is down
private void Accelerate()
{
Velocity.X = (float)Math.Cos(Rotation) * TangentalVelocity;
Velocity.Y = (float)Math.Sin(Rotation) * TangentalVelocity;
}
//Called once per update
private void Deccelerate()
{
Velocity.X = Velocity.X -= Friction * Velocity.X;
Velocity.Y = Velocity.Y -= Friction * Velocity.Y;
}
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1;
SoundManager.Vulture.Play();
}
//screen edge detection
public void CheckForOutOfScreen()
{
//Check if ABOVE screen
if (Position.Y - Origin.Y / 2 < GameEngine.Viewport.Y) { OnExitScreen(); }
else
//Check if BELOW screen
if (Position.Y + Origin.Y / 2 > GameEngine.Viewport.Height) { OnExitScreen(); }
else
//Check if RIGHT of screen
if (this.Position.X + Origin.X / 2 > GameEngine.Viewport.Width) { OnExitScreen(); }
else
//Check if LEFT of screen
if (this.Position.X - Origin.X / 2 < GameEngine.Viewport.X) { OnExitScreen(); }
else
{
if (OnScreen == false)
OnScreen = true;
}
}
virtual public void OnExitScreen()
{
OnScreen = false;
Bounce();
}
Let's see if I understood correctly. First, you rotate your sprite. After that, you accelerate it forward. In that case:
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1; //I THINK THIS IS THE PROBLEM
SoundManager.Vulture.Play();
}
Let's suposse your sprite has no rotation when it looks up. In that case, if it's looking right it has rotated 90º, and its speed is v = (x, 0), with x > 0. When it goes out of the screen, its rotation becomes -90º and the speed v = (-x, 0). BUT you're pressing the up key and Accelerate method is called so immediately the speed becomes v = (x, 0) again. The sprite goes out of the screen again, changes its velocity to v = (-x, 0), etc. That produces the weird bounce.
I would try doing this:
private void Bounce()
{
Rotation = Rotation * -1;
SoundManager.Vulture.Play();
}
and check if it works also up and bottom. I think it will work. If not, use two different Bounce methods, one for top/bottom and another one for left/right.
Your second question... It's a bit difficult. In Physics, things reach a max speed because air friction force (or another force) is speed-dependent. So if you increase your speed, the force also increases... at the end, that force will balance the other and the speed will be constant. I think the best way to simulate a terminal speed is using this concept. If you want to read more about terminal velocity, take a look on Wikipedia: http://en.wikipedia.org/wiki/Terminal_velocity
private void Accelerate()
{
Acceleration.X = Math.abs(MotorForce - airFriction.X);
Acceleration.Y = Math.abs(MotorForce - airFriction.Y);
if (Acceleration.X < 0)
{
Acceleration.X = 0;
}
if (Acceleration.Y < 0)
{
Acceleration.Y = 0;
}
Velocity.X += (float)Math.Cos(Rotation) * Acceleration.X
Velocity.Y += (float)Math.Sin(Rotation) * Acceleration.Y
airFriction.X = Math.abs(airFrictionConstant * Velocity.X);
airFriction.Y = Math.abs(airFrictionConstant * Velocity.Y);
}
First, we calculate the accelartion using a "MotorForce" and the air friction. The MotorForce is the force we make to move our sprite. The air friction always tries to "eliminate" the movement, so is always postive. We finally take absolute values because the rotation give us the direction of the vector. If the acceleration is lower than 0, that means that the air friction is greater than our MotorForce. It's a friction, so it can't do that: if acceleration < 0, we make it 0 -the air force reached our motor force and the speed becomes constant.
After that, the velocity will increase using the acceleration. Finally, we update the air friction value.
One thing more: you may update also the value of airFriction in the Deccelarate method, even if you don't consider it in that method.
If you have any problem with this, or you don't understand something (sometimes my English is not very good ^^"), say it =)
I made a 2D isometric renderer. It works fine but now I want to show my scene from 4 differents point of view (NO NW SE SW) but, on a 90° rotation, my camera cannot keep the center of my scene on screen.
What's working :
I calcul new projection of scene to match the new viewport (x y z in my world).
I reorganise part of my scene(chunk) to draw them in a correct order
I reorganise 'tiles' of 'chunks' to draw them in a correct order
I can keep the correct center with a 180 degres rotation.
What's do not working :
I cannot find a correct translation to apply to my camera after a 90 degres rotation.
What I know :
To keep the same center on a 180° rotation with my camera I have to do this :
camera.Position -= new Vector2(2 * camera.Position.X + camera.Width, 2 * camera.Position.Y + camera.Height);
Illustration
If the center of your map is origo (0,0,0), this gets easy:
First you store your default camera position in a Vector3 CameraOffset, then you calculate position using a rotation-matrix. 90* in redians is half a Pi, so we will use PiOverTwo. We will also use an enum to decide what direction to be pointing, so you can say
Camera.Orientation = Orientation.East;
and the camera should fix itself :)
public enum Orientation
{
North, East, South, West
}
in camera:
public Vector3 Position { get; protected set; }
Vector3 _CameraOffset = new Vector3(0, 20, 20);
public Vector3 CameraOffset
{
get
{
return _Orientation;
}
set
{
_Orientation = value;
UpdateOrientation();
}
}
Orientation _Orientation = Orientation.North;
public Orientation Orientation
{
get
{
return _Orientation;
}
set
{
_Orientation = value;
UpdateOrientation();
}
}
private void UpdateOrientation()
{
Position = Vector3.Transform(CameraOffset, Matrix.CreateRotationY(MathHelper.PiOverTwo * (int)Orientation));
}
If you want a gliding movement between positions, I think I can help too ;)
If your camera does not focus on Vector3.Zero and should not rotate around it, you just need to change:
Position = Vector3.Transform(CameraOffset, Matrix.CreateRotationY(MathHelper.PiOverTwo * (int)Orientation));
to:
Position = Vector3.Transform(CameraOffset, Matrix.CreateRotationY(MathHelper.PiOverTwo * (int)Orientation) * Matrix.CreateTranslation(FocusPoint));
Here, FocusPoint is the point in 3D that you rotate around (your worlds center). And now you also know how to let your camera move around, if you call UpdateOrientation() in your Camera.Update() ;)
EDIT; So sorry, totally missed the point that you use 2D. I'll be back later to see if I can help :P
I have a manometer, this needs to spin from a minimum value to a maximum. right now I have the manometer as a picture and the arrow as a movieclip. I've got it spinning at the right speed, but don't know how to stop it at the lowest/highest pressure.
It's suppose to work like this:
I have two movieclip/buttons; one for simulating more pressure, and one for less.
when the user presses the "more pressure" movieclip/button the pressure begins to rise and the arrow inside the manometer begin to turn.
At the same time as the pressure rises, another movieclip ("stamp") will push uppwards.
then another movieclip/button, "less pressure" simulates pressure drop; when pressed, the same point as the arrow stopped at when pressure rised, will start sinking towards minimum, and the stamp will go down again.
so, when the user presses "more pressure" pressure rises towards maximum, and as soon as the user stop pressing the button, the animation stops (both the stamp and the arrow). And if the user presses "lower pressure", the arrow starts sinking from where it stopped.
heres my code so far: pil1 = manometerarrow, the stamp = stamp, and "less pressure"/"more pressure" = Lpress / mpress
addEventListener (Event.ENTER_FRAME, rotate);
function rotate(event:Event):void
{
pil1.rotation = pil1.rotation+1;
}
ymutlu is on the right track. The mouse down event will only execute once when the mouse is depressed. To make the object rotate continuously while the mouse is depressed you need to increment or decrement the rotation of the object on each frame. I think the following should do the trick:
import flash.events.MouseEvent;
import flash.events.Event;
var rotate = 0;
Hpress.addEventListener(MouseEvent.MOUSE_DOWN, Hpressed);
Hpress.addEventListener(MouseEvent.MOUSE_UP, removeEnterFrame);
Lpress.addEventListener(MouseEvent.MOUSE_DOWN, Lpressed);
Lpress.addEventListener(MouseEvent.MOUSE_UP, removeEnterFrame);
function Hpressed(e:MouseEvent):void
{
rotate = 1;
addEnterFrame();
}
function Lpressed(e:MouseEvent):void
{
rotate = -1;
addEnterFrame();
}
function addEnterFrame():void
{
this.addEventListener(Event.ENTER_FRAME, update);
}
function removeEnterFrame(e:MouseEvent):void
{
this.removeEventListener(Event.ENTER_FRAME, update);
}
function update(e:Event):void
{
pil1.rotation += rotate;
}
hold to varaible that states if max button down or min button down, and check it in enterframe loop. Edited answer on your comment, hope you can sort it out.
addEventListener (Event.ENTER_FRAME, rotate);
function rotate(event:Event):void
{
if(isMaxDown) // true when max button down
pil1.rotation = Math.min(presMax,pil1.rotation+1); // presMax is max value that pressure can go
if(isMinDown) // true when min button down
pil1.rotation = Math.max(presMin,pil1.rotation-1);// presMin is min value that pressure can go
}
// isMaxDown and isMinDown are global values.
Hpress.addEventListener(MouseEvent.MOUSE_DOWN, Hpressed);
Lpress.addEventListener(MouseEvent.MOUSE_DOWN, Lpressed);
Hpress.addEventListener(MouseEvent.MOUSE_UP, H_up);
Lpress.addEventListener(MouseEvent.MOUSE_UP, L_up);
function H_up(e:MouseEvent):void {
isMaxDown=false;
}
function L_up(e:MouseEvent):void {
isMinDown=false;
}
function Hpressed(e:MouseEvent):void {
isMaxDown=true;
}
function Lpressed(e:MouseEvent):void {
isMinDown=true;
}
This code would help you but prob this is not a path to fallow to do somthing like that.
I have an object I need to rotate by clicking and dragging. Following some AS2 code I got the object to rotate a bit every time the mouse is clicked, but can't get it to work with drag.
needle.addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag_2);
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop_2);
function fl_ClickToDrag_2(event:MouseEvent):void
{
var angle = Math.atan2(mouseY-needle.y,mouseX-needle.x);
// apply rotation to handle by converting angle into degrees
needle.rotation = angle*180/Math.PI;
// rotate the grip opposite the handle so it won't rotate along with it
//this.grip._rotation = -this._rotation;
}
function fl_ReleaseToDrop_2(event:MouseEvent):void
{
needle.stopDrag();
}
Well the problem I see is that the MOUSE_DOWN event only fires once per click, so you only run the code in the handler once.
There could be a better way than this but this is how I'd consider doing it:
EDITED FOR DETAIL:
public class Test extends MovieClip {
private var n:Needle;
public function Test() {
// constructor code
n = new Needle();
stage.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownF,false,0,true);
stage.addEventListener(MouseEvent.MOUSE_UP,mouseUpF,false,0,true);
n.x = stage.stageWidth/2; //center needle on stage
n.y = stage.stageHeight/2;
addChild(n); //add needle to stage
}
public function mouseDownF(e:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,rotate,false,0,true);
}
public function rotate(e:MouseEvent):void {
var angle:Number = Math.atan2(mouseY - n.y,mouseX - n.x); //get angle in radians (pythagoras)
angle = angle * 180/Math.PI -90; //convert to degrees , the 90 is to have it point to the mouse
n.rotation = angle; //rotate
}
public function mouseUpF(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,rotate);
}
}
So when the user clicks down (mouseDown) it activates an event listener that fires the rotate handler every time the mouse moves. When the user lets go of the click the event listener is destroyed. The false,0,true); when adding the event listener is to make it a weakly referenced listener so that it gets collected by the garbage collector and doesn't just sit in memory taking up space forever.