[SOLVED]How to create a spiraling movement behavior using tweens!


(Helios) #1

Note for those encountering this by search:

NotARaptor solves this solution elegantly and brilliantly, but I was too foolish to understand his solution. I ultimately fixed my own solution, although the result looks imperfect (I just happened to like the imperfect look). I will present both solutions here.

The PROBLEM

okay, so the look im going for is fairly simple, but the implementation is tricky. The idea is to create a spiraling pattern that moves from the subject to the target at any angle, heres my worst possible rendition of what this might look like drawn in MS paint:

My SOLUTION

Let me go through my though process so far.

so, we start with a var direction:Point which holds coordinates on a unit circle between 0 and 1 – we found these by using our friends sine and cosine of the angle to the mouse coordinates from the subject.

Essentially, this point holds the direction we want to fire the spiral bullet by finding what percentage of your speed you ought to move in the X direction, and what percent in the Y direction:

So far, so good. Next I want to find the angle of the line perpendicular to this function. we find this by taking the angle of the first direction

   var angle:Number = Math.atan2(dir.x, dir.y)

and subtracting it from 90 degrees to get the complementary angle.

var pAngle:Number = -rightAngle + angle

Again, we’re working with the unit circle, so our values are between 0 and 1. This helps us with our math, because we KNOW that the HYPOTENUSE on a unit circle is ALWAYS equal to 1.

we find our perpendicular direction x and y using sine and cosine again:

okay, so far so good, so now we have two unit circle coordinates, representing the movement components ALONG the target vector and PERPENDICULAR TO the target vector

var dir:Point;
var dirPerpendicular:Point;

The final code works like this:

// determine angle of movement
var angle:Number = Math.atan2(dir.x, dir.y)

// determine complement
var complAngle:Number = ( -rightAngle + angle)

// determine perpendicular compnents
var perpX:Number = Math.cos(complAngle)
var perpY:Number = Math.sin(complAngle)

// determine displacement.  Multiply the unit circle values by the speed to get the x and y
// components of movement
var normalDisplacement:Point = new Point(dir.x * speed, dir.y * speed)
var perpendicularDisplacement:Point = new Point(perpX *speed/2, perpY * speed/2)
if (!rising)
{
	 perpendicularDisplacement.x *= -1
	perpendicularDisplacement.y *= -1
}

// determine  the new final positions by summing the displacements 
var newX:Number = normalDisplacement.x + perpendicularDisplacement.x;
var newY:Number = normalDisplacement.y + perpendicularDisplacement.y;

// tween the values to their new locations.  Of the two values,
// dir.x and dir.y, the larger value should be tweened linearly, and
// the smaller value should use Ease.sineInOut

aTween.tween(subject, "x", subject.x + newX, time, Math.abs(dir.x)>Math.abs(dir.y)? null : Ease.sineInOut)

bTween.tween(subject, "y", subject.y + newY, time, Math.abs(dir.x)>Math.abs(dir.y)? Ease.sineInOut: null)

it still looks a little unlike my original vision on the diagonals, but it creates the effect i’m going for, and in Actually, I think the look of the diagonal spiral is pretty cool! lets face it, I’m not using this for drone strike targeting, I’m just making a video game. Pretty cool looking is the best I need to create.

press the “e” key to fire

(for reference this is the earlier, screwed up version, the working code is down at the bottom) FromScratch.swf(65.6 KB)

NotARaptor’s SOLUTION

see below


Homing in on a player in a way that looks nice?
A oval motion (entity teleport some pixels to his orbit)[SOLVED]
(Bora Kasap) #2

So, you have to use tweens?


(JP Mortiboys) #3
package  {
	import flash.display.BitmapData;
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Emitter;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.tweens.motion.LinearPath;
	import net.flashpunk.utils.Input;
	/**
	 * ...
	 * @author NotARaptor
	 */
	public class Wiggler extends Entity {
		
		private var _currentPath:LinearPath;
		
		private var emitter:Emitter;
		
		public function Wiggler() {
			super(FP.halfWidth, FP.halfHeight);
			
			var gfx:Image = Image.createCircle(16, 0x339933);
			gfx.centerOrigin();
			
			addGraphic(gfx);
			
			emitter = new Emitter(new BitmapData(4, 4, true, 0xFF44AA44));
			emitter.newType("thing");
			emitter.setAlpha("thing");
			emitter.setMotion("thing", 0, 0, 5.0, 0, 0, 5);
			emitter.visible = true;
			emitter.relative = false;
			addGraphic(emitter);
		}
		
		override public function update():void {
			super.update();
			
			emitter.emit("thing", x, y);
			
			if (Input.mousePressed) {
				setTarget(world.mouseX, world.mouseY);
			}
		}
		
		public function setTarget(x:Number, y:Number):void {
			// Immediately stop any current motion
			if (_currentPath && _currentPath.active) {
				_currentPath.cancel();
			}
			
			// How many wiggles will be in the path per 100 pixels of linear distance (wiggliness)
			const WIGGLES_PER_100_PIXELS:Number = 2.0;
			// The maximum amplitude of the wave (perpendicular length)
			const MAX_WIGGLE_AMPLITUDE:Number 	= 50.0;
			// How many points to add to the path per 100 pixels of linear distance (smoothness)
			const POINTS_PER_100_PIXELS:Number	= 25.0; 
			
			var dx:Number = x - this.x;
			var dy:Number = y - this.y;
			
			var len:Number = Math.sqrt(dx * dx + dy * dy);
			var numWiggles:int = int(WIGGLES_PER_100_PIXELS * len / 100);
			var coef:Number = Math.PI * Number(numWiggles);
			var numPoints:int = int(POINTS_PER_100_PIXELS * len / 100);
			
			var dt:Number = 1.0 / Number(numPoints);
			var t:Number = 0.0;
			
			// Normalize the vector now
			dx /= len;
			dy /= len;
			
			_currentPath = new LinearPath();
			
			var displacement:Number, amplitude:Number;
			
			for (var iPoint:int = 0; iPoint < numPoints; iPoint++, t += dt) {
				displacement = len * t;
				amplitude = Math.sin(t * coef) * MAX_WIGGLE_AMPLITUDE;
				_currentPath.addPoint(
					this.x + displacement * dx - amplitude * dy,
					this.y  +displacement * dy + amplitude * dx
				);
			}
			
			_currentPath.object = this;
			addTween(_currentPath, false);
			_currentPath.setMotionSpeed(500);
		}
		
	}

}

(JP Mortiboys) #4

WigglePath.swf(93.0 KB)

Aargh! Is there no way to embed or link to a SWF file that works in the browser and doesn't download it?! I can't access my normal server at the moment so I can't host the example.

:cool:

dropbox to the rescue!


(Bora Kasap) #5

yeah, you can use pastehtml.com to embed your swf files, but you need to upload swf’s elsewhere…


(JP Mortiboys) #6

I ended up using dropbox, but thanks, I’ll remember that one.


(Helios) #8

@AlobarNon

I would really like to. Part of the idea behind this exercise is attempting to maximize the use of tweens to quickly create complex behaviors in my work.

@NotARaptor

I can’t thank you enough for sitting down and working through that problem. It looks like you kind of built your own tweening engine there.

I think I probably should have specified, I want to use tweens in my answer, because I’m studying the use of tweens as shortcuts (I had always built things from scratch like this before – and as you can see I sucked at the little trig I attempted, so now I want to avoid it altogether). Also, preferably I would like to make use of the Unit Circle coordinates in the answer, because the function is already being passed that information anyway (that same information is used for all movement processing in the game right now)

Also, this is a very interesting step! Is this common practice? Its always been difficult for me to keep track of positives and negatives on my screen, so this could be a huge benefit. Does this center the origin on the entity in question?

@Everyone

Is there a particular reason both of you wanted to avoid tweening? are there limitations to tweening that I should be aware of?


(JP Mortiboys) #9

Heh, no worries, let’s see if we can’t straighten a few points out here!

Not at all - I am using tweens! I’m using a LinearPath which is-a Motion which is-a Tween. I’m just using rudimentary geometry to create the points which make up the path.

It’s certainly possible to forgo the linear path bit and create your own tween to calculate things directly, the problem here is that it will be nigh-on impossible to get the movement to be at a constant speed (or however you want your speed), because of the parametrisation. By approximating your complex path as a piecewise linear path, the speed control is automatic.

Furthermore, by pre-calculating the path in this manner it means we can fit a whole number of “wiggles” into the movement, so that the perpendicular displacement both at the beginning and at the end of the movement is zero. This would be harder to accomplish if you were calculating on the fly.

If by that you mean that you want to use tweens as in your example above - one for x and one for y, that’s not really going to happen, not if you want the movement to be able to happen at any angle.

You see, currently you’re tweening x linearly and y in a wavey manner - that’s fine if the motion is going to be horizontal, but if it’s vertical this won’t work, and if it’s diagonal it will look plain wrong. This is because as soon as your target vector isn’t horizontal any more, the local coordinates in the wave/squiggle don’t have the same basis as the screen coordinates that x and y represent (screen x now depends on both squiggle x and y, and screen also y depends on both squiggle x and y).

What you could do is tween separate parameters - let’s call them u and v, the first linear and the second wavey. Then you could calculate x and y by using u as the displacement ALONG the target vector and v as the displacement PERPENDICULAR TO the target vector.

This, incidentally, is exactly what my code does - the point loop at the end of it performs precisely this calculation.


(Helios) #10

Actually, I was referring to this first point

var dir:Point;

which represents the direction of movement (both are numbers between 0 and 1). I realize, however, that if I multiply these by speed, then I have the target x and y that you used. here:

So, in conclusion, this is going to take me a while to mentally unpack. Thanks again so much, I’ll be studying your code for a while.


(JP Mortiboys) #11

No, nothing so fancy, it just centres the entity’s graphic on its position - so the entity’s x and y correspond to the centre of the circle rather than its top-left.


As for the other thing, it’s not much of a difference - I’m taking the approach that you want to fire the wiggle AT something; if instead you want to fire it in a specific direction it’s only slightly different.


(Helios) #12

Excellent!

Yeah, this is what I’m going to work on. I want it to perform exactly one wiggle in the direction of my choosing, and I’ll probably try to use the U and V concepts to accomplish that.


(Helios) #13

DERP

my trig was all wrong. Thank you guys for being so polite and waiting for me to figure that out hahaha

Why in the world did I think I was looking for the tangent?

OKAY so, my solution works now with this change:

var perpX:Number = Math.cos(complAngle)
var perpY:Number = Math.sin(complAngle)

and this change (I snuck in a ternary operator in the easing function to test whether we moved vertically or horizontally):

aTween.tween(subject, "x", subject.x + newX, time, Math.abs(dir.x)>Math.abs(dir.y)? null : Ease.sineInOut)

bTween.tween(subject, "y", subject.y + newY, time, Math.abs(dir.x)>Math.abs(dir.y)? Ease.sineInOut: null)

it still looks a little unlike my original vision on the diagonals, but it creates the effect i’m going for, and in Actually, I think the look of the diagonal spiral is pretty cool! lets face it, I’m not using this for drone strike targeting, I’m just making a video game. Pretty cool looking is the best I need to create.

FromScratch.swf(65.6 KB)