as3 applying gravity to a missile - actionscript-3

I'm trying to create a "worms" style turn based artillery game is as3. I am able to add my ball on the x/y location of the character and have the ball fire in the direction of the mouses location when the click is made but I am now having difficulty in applying gravity to the ball. If anybody can help me at all with what I need to do to make the ball drop in an arc after being fired that would be great.
My code for adding the ball and having it fire in the selected direction is:
Game Main:
function redFollow(e:MouseEvent):void
{
var a1 = mouseY - redPower.y;
var b1 = mouseX - redPower.x;
var radians1 = Math.atan2(a1,b1);
var degrees1 = radians1 / (Math.PI/180);
redPower.rotation = degrees1;
}
public function redFIRE(Event:MouseEvent)
{
removeChild(redPower);
turnchangeover();
addChild(RPBall);
trace("FIRE!");
RPBall.x = red.x;
RPBall.y = red.y;
RPBall.rotation = redPower.rotation + 90;
}
and then the ball's class code is:
package {
import flash.display.MovieClip;
import flash.events.Event;
public class redBall extends MovieClip {
public function redBall() {
this.addEventListener(Event.ENTER_FRAME, moveShot);
}
function moveShot(event:Event){
var thisMissile:redBall = redBall(event.target);
var _missileSpeed:int = 7;
var gravity:int = 0.8;
//get the x,y components of the angle
var missileAngleRadians = ((-thisMissile.rotation - 180) * Math.PI /180);
//trace("missle angle: " + missileAngleRadians);
var yMoveIncrement = Math.cos(missileAngleRadians) * _missileSpeed;
var xMoveIncrement = Math.sin(missileAngleRadians) * _missileSpeed;
thisMissile.y = thisMissile.y +yMoveIncrement;
thisMissile.x = thisMissile.x + xMoveIncrement;
trace(thisMissile.y);
}
}
}

You need to store speed separately by X and by Y. See, after your ball has been launched, you don't care about angle, but only care about speed, and speed is what's affected by gravity. You already split speed by X and Y components in yMoveIncrement,xMoveIncrement, now you just need to have them be properties of the ball (it'll be its velocity), and add a small per-frame increment to Y component of velocity.
public class redBall extends MovieClip {
public var yVelocity:Number=0;
public var xVelocity:Number=0; // these will be assigned when the ball is shot
public static var gravity:Number=0.5; // Number, not "int"
// also "static" so that it'll affect all balls similarly
public function redBall() {
this.addEventListener(Event.ENTER_FRAME, moveShot);
}
function moveShot(event:Event){
// var thisMissile:redBall = redBall(event.target);
// why is ^ this? You can use "this" qualifier in this function
// we already have the x,y components of the angle
this.y = this.y + this.yVelocity;
this.x = this.x + this.xVelocity;
this.yVelocity = this.yVelocity + gravity;
// also, you care for rotation - let's add a trick
var angle:Number=Math.atan2(this.yVelocity,this.xVelocity);
// direction angle, in radians
this.rotation=angle*180/Math.PI; // make into degrees and rotate
trace(thisMissile.y);
}
}
It'll be a good practice if you will add a shoot function to redBall class that will accept angle and initial velocity, to assign xVelocity and yVelocity as you did in the listener.

In order to re-create the that projectile motion you'll need to introduce 3 variables Gravity (constant), velocity (on the x and y, similar to your move increment) and decay, also referred to as friction.
Friction should be a value under 1. So that it reduces velocity over time. Once the velocity is less than the Gravity in your system the item should start dropping back to the ground. The item will also slow down on the x axis.
velocity.y *= friction;
thisMissile.y += velocity.y + gravity.y;
Here's a generic tutorial on projectile physics that may be of interest and an AS3 tutorial on Angry Bird like ground to ground projectile.

Related

How to create smooth motion for a mouse follower along a predefined path?

I want to make a tracing game. I want my circle to follow the path as the user traces the letter (path of the letter). The user can not go back to the area which is already traced
import flash.events.Event;
import flash.geom.Point;
var i: Number;
var size: int = 80;
var down: Boolean = false;
var up: Boolean = true;
var inside: Boolean = true;
var outside: Boolean = true;
var circle: Shape = new Shape();
stage.addEventListener(Event.ENTER_FRAME, loop);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseup);
char.addEventListener(MouseEvent.MOUSE_DOWN, mousedown);
function loop(e: Event) {
if (down == true) {
// Checks if mouse pointer is on path i.e 'S' alphabet
if (s.hitTestPoint(stage.mouseX, stage.mouseY, true)) {
inside = true;
outside = true;
var point: Point = maskobj.globalToLocal(new Point(stage.mouseX, stage.mouseY));
var point2: Point = new Point();
//Checks if mouse pointer is completely outside of drawn area
for (i = 0; i < 2 * Math.PI; i += (2 * Math.PI) / 10) {
point2.x = stage.mouseX + (size / 3) * Math.cos(i);
point2.y = stage.mouseY + (size / 3) * Math.sin(i);
if ((maskobj.hitTestPoint(point2.x, point2.y, true))) {
outside = false;
break;
}
}
//Checks if mouse pointer is completely inside drawn area
for (i = 0; i < 2 * Math.PI; i += (2 * Math.PI) / 10) {
point2.x = stage.mouseX + (size / 3) * Math.cos(i);
point2.y = stage.mouseY + (size / 3) * Math.sin(i);
if (!(maskobj.hitTestPoint(point2.x, point2.y, true))) {
inside = false;
break;
}
}
//Character will be moved only if mouse position not to far from current position
if (outside == false) {
if (inside == false) {
//Increases drawn area by drawing a circle shape in 'maskobj' MovieClip
circle.graphics.beginFill(0x0000ff);
circle.graphics.drawCircle(point.x, point.y, size);
circle.graphics.endFill();
maskobj.addChild(circle);
//Moves character to new position
char.x = stage.mouseX;
char.y = stage.mouseY;
}
}
}
}
}
function mouseup(e: MouseEvent): void {
up = true;
down = false;
}
function mousedown(e: MouseEvent): void {
down = true;
up = false;
}
When I trace the path,the motion is not smooth. Can someone please suggest a way to make the motion smooth OR suggest another way to achieve the same. Thank you in advance.
I've created a drawing game before that allowed the user to draw a path.
Not sure why Wicked's answer was down-voted, as the first thing you need to do is to use the highest frame rate that you can get away with. The higher the frame rate, the smoother your curve.
I see that your code draws a circle at the current position if the conditions are met. It might be better to draw a line from the last point.x/point.y to the current one instead of just a circle, so that you don't have any holes in your path.
I couldn't get around the fact that the line was jagged (a series of straight lines) as it was being drawn, but as soon as the user lifted their finger I was able to take the points along the line they had drawn and replace them with a smooth bezier Path (a series of simple bezier curves), which worked well. You could also do this on-the-fly once you have 3 points (you need 3 points to draw a curve).
Here is a good reference on how to achieve this, with theory and code samples. See further down the page for bezier paths. You'll need to convert to AS3, but it shouldn't be difficult.
Another tip is to do as little calculation as possible within the ENTER_FRAME. You could pre-calculate the two values used by your loops (2 * Math.PI) and ((2 * Math.PI) / 10) as these are constants. You could also calculate (size/3) once at the top of the function, and especially pre-calculate the 10 values for Math.sin(i) and Math.cos(i) and store them in an Array (basically a LUT - Look Up Table) as these are the heaviest math ops you're doing.
My final tip is that your code doesn't check if the point being drawn is very close to the last point that was drawn. I would recommend you do this, and only draw a point after the mouse has moved a minimum distance (e.g. 2 pixels). Otherwise you could get the mouse sitting still in one spot and your code is drawing circle upon circle on top of itself needlessly.
Try increasing the FPS in your document to atleast double what you currently have
Modify>Document...>Frame Rate

An analogue of the game "Worms". Physics. as3

Help please write a function of the projectile bounce off the ground. How do I find the angle of the collision, and what should be the reflection angle or at least learn the point of collision?
package {
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.geom.Point;
import flash.geom.Matrix;
import flash.events.Event;
import flash.events.MouseEvent;
public class worms extends Sprite {
public var terrain_bmpd=new BitmapData(550,200,true,0xFF00FF00);//This is the Bitmap of the terrain
public var terrain_bmp=new Bitmap(terrain_bmpd);//and this the BitmapData
public var character=new Sprite();//The character will work as a worm
public var hole=new Sprite();//That's the hole we need
public var hole_matrix:Matrix;//The hole_matrix is used to set the position of the hole
public var left_foot:Point;
public var right_foot:Point;//These are the feet of the character. We will use it to check collisions
public function worms() {
draw_objects();//This function draws the character, the terrain and the hole.
stage.addEventListener(Event.ENTER_FRAME,fall);
}
public function fall(e:Event) {
/*This function will move down the character if there isn't a collision
between the terrain and the "feet" of the character*/
for (var i:int=0; i<10; i++) {//We want to check every pixel if there's acollision, so we won't move the character 10 pixels all at once
left_foot=new Point(character.x-5,character.y+10);
right_foot=new Point(character.x+5,character.y+10);
if (!(terrain_bmpd.hitTest(new
Point(terrain_bmp.x,terrain_bmp.y),0x01,left_foot))&&!(terrain_bmpd.hitTest(new Point(terrain_bmp.x,terrain_bmp.y),0x01,right_foot))) {
character.y++;//If there aren't any collisions, make the character fall one pixel
}
}
}
public function draw_objects() {
terrain_bmp.y=200;//The terrain shouldn't be at the top of the stage!
stage.addChild(terrain_bmp);//We can make the terrain visible
character.graphics.beginFill(0x0000FF);//Let's draw the character. It will be a blue rectangle.
character.graphics.drawRect(-5,-10,10,20);
character.x=250;
stage.addChild(character);
hole.graphics.beginFill(0x000000);//Now we draw the hole. It doesn't matter the colour.
hole.graphics.drawCircle(0,0,30);
}
}
}
There's several parts to this, and it seems you don't really know where to begin.
Like the others have mentioned, your object (projectile, character, etc) movement should be represented by a vector, ie x and y velocity. (A Point can represent a vector.)
Next, to detect angle of collision on pixel-based terrain like you have, you can do a series of point (pixel) hit tests in a radius around the object. This will give you a collection of hit points, which you define as vectors from the center of the object. Average the vectors and you have your collision angle.
Here's a function I've used to do this:
/**
* Test for a hit against a shape based on a point and radius,
* returns the angle of the hit or NaN if no hit.
* Like hitTestPoint, xPos and yPos must be in stage coordinates.
*/
function hitTestAngle(shape:Sprite, xPos:Number, yPos:Number, radius:Number, samples:uint = 90):Number {
const PI2:Number = Math.PI * 2, SAMPLE:Number = 1 / samples;
var dx:Number, dy:Number, a:Number, tx:Number = 0, ty:Number = 0, hits:int = 0;
var i:int = samples;
while(i--){
a = PI2 * (i * SAMPLE);
dx = radius * Math.cos(a);
dy = radius * Math.sin(a);
if(shape.hitTestPoint(xPos + dx, yPos + dy, true)){
hits++;
tx += dx;
ty += dy;
}
}
if(!hits)
return NaN;
return Math.atan2(ty, tx) * (180 / Math.PI);
}
This uses hitTestPoint() but since your terrain is BitmapData you could just use getPixel32() and check the alpha value.
You could also use Collision Detection Kit, which uses a similar approach under the hood, with a bunch of extra features.
Once you have the angle of collision, you can define a perpendicular vector as the collision surface to reflect the projectile's vector.
That should get you started.
Flat, horizontal ground:
//Check next movementstep
if((vel_y + pos_y) > terrainBottom) {
vel_y = -vel_y*DampingFactor;
}
for the general case you need to transform vel_x, vel_y into the case shown above using trigonometric functions.
As in the previous answer, use vel_x and vel_y to represent the motion of your character in vector form. Use these values to increase your x and y co-ordinates at each iteration. In your example you are using vel_x = 0, vel_y = 1, because you are increasing the y-coordinate by 1 each time.
If a surface has an an angle A measured counter-clockwise from the horizontal then x_vel and y_vel after bouncing (relfecting) on the surface will be
x_vel.cos2A + y_vel.sin2A and x_vel.sin2A - y_vel.cos2A respectively. To see how this is derived see planetmath
This is for a perfectly elastic collision, ie no loss of speed on impact.

Actionscript 3: Rotate multiple shapes around internal point

I'm having trouble with AS3 which I have to use for a little research project of mine.
The AS3 project would create a number of randomly placed squares which all would rotate around its center point.
I managed to figure out how to rotate it internally using this handy walkthrough.
However I am having trouble applying this method to all squares which were created in a for loop with randomly selected stage points. Only the first created will rotate
Here is the code in question:-
for(var i=0; i<10; i++)
{
var square:Shape = new Shape();
this.addChild(square);
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square.x=posX;
square.y=posY;
var curSquareAng:Number=0;
var squareRotCenter:Point=new Point(0,0);
drawShapes();
var squareMat:Matrix=square.transform.matrix.clone();
}
this.addEventListener(Event.ENTER_FRAME, onEnter);
function onEnter(e:Event):void {
curSquareAng = (curSquareAng+2)%360;
rotateSquare(curSquareAng);
}
function rotateSquare(deg:Number):void {
var mat:Matrix= squareMat.clone();
MatrixTransformer.rotateAroundInternalPoint(mat,squareRotCenter.x, squareRotCenter.y, deg);
square.transform.matrix = mat;
}
I realize I likely have to create an array for each of the squares initial center points and loop through them. However I am completely lost on how to do so. As you can likely tell I am unfamiliar with AS3 and would much appreciate any help you can give this beginner programmer. :P
You need to create your own class based on a shape, then you stuff it full of properties that represent rotation center point, current angle and whatever else you'd want your squares to contain, then give the class update method which will do what you wrote in onEnter function for itself only. Then you will have easier control over what your squares are able to do. This technique is called "encapsulation".
On a side note, if you're wanting your square to rotate around internal point of (0,0), you can set their rotation property to achieve the desired effect. For other points, the walkthrough or its equivalent should be used.
public class Square extends Shape {
public var rotationCenter:Point=new Point();
private var currentAngle:Number=0;
public var rotationSpeed:Number=2; // degrees per frame
private var baseMatrix:Matrix;
public function Square() {
// draw the shape on "this.graphics"
this.graphics.beginFill(0xffff00,1);
this.graphics.moveTo(-20,-20);
this.graphics.lineTo(20,-20);
this.graphics.lineTo(20,20);
this.graphics.lineTo(20,-20);
this.graphics.lineTo(-20,-20);
this.graphics.endFill();
// if not set in declaration, set internal vars
baseMatrix=this.transform.matrix; // likely identity matrix, but let's initialize anyway
}
public function storeMatrix():void {
// you are positioning a square after you create it, so probably you want its new location to be transformed
// that one's matrix will no longer be an identity, so go capture
baseMatrix=this.transform.matrix;
}
public function update():void {
// should be called once per frame
currentAngle=(currentAngle+rotationSpeed)%360;
var mat:Matrix= baseMatrix.clone();
MatrixTransformer.rotateAroundInternalPoint(mat,rotationCenter.x, rotationCenter.y, currentAngle);
this.transform.matrix = mat;
}
}
Now, you will have to maintain an array of squares to make them rotate separately:
var squares:Array=[];
for (var i:int=0;i<10;i++) {
var square:Square=new Square();
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square.x=posX;
square.y=posY;
// after you position, give it a rotation point
square.rotationCenter.x=Math.random()*40-20;
square.rotationCenter.y=Math.random()*40-20; // -20 to 20, should do for this example
// now fix the position so your square will know that it should rotate
// its *current* transform matrix
square.storeMatrix();
// even if it's actually unchanged by changing X or Y
// also, should you desire to scale some square, you should do that prior to calling this
// now add the square to an array
squares.push(square);
}
addEventListener(Event.ENTER_FRAME,onEnter);
function onEnter(e:Event):void {
for (var i:int=0;i<squares.length;i++) squares[i].update();
// simple, isn't it? Each square will know what to do.
}
Nevermind. Thank you Vesper for putting me on the right track I've managed to solve my problem thanks to your input (not necessarily through your way but your input helped me get to the destination per say).
I think I was making it a little too complicated going though the matrix route and instead used a shape array to loop through the squares and add rotation. The solution I came to is a little simple but gets the job done.
public var rotationSpeed:Number=2; // degrees per frame
public var square:Array = new Array( );
public function Square() {
for (var i:int=0;i<10;i++) {
square[i] = new Shape();
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square[i].graphics.lineStyle();
var rgb = Math.random() * 0xFFFFFF;
square[i].graphics.beginFill(rgb);
// -50 determines where the spin will center from.
square[i].graphics.drawRect(-50,-50,100,100);
square[i].graphics.endFill();
square[i].x = posX;
square[i].y = posY;
addChild(square[i]);
}
addEventListener(Event.ENTER_FRAME,onEnter);
}
private function onEnter(e:Event):void {
for (var i:int=0; i < square.length; i++) {
getChildAt(i).rotation += rotationSpeed;
}
}

How can I use Action Script 3.0 to make random placed Symbols fly by?

I'm trying to make a simple animation with Flash CS4 and Action Script 3.0 to make a number of Symbols fly by from right to left constantly. What I want is that once a symbol has reached the end of the screen it is destroyed and another one is placed at the start position.
I intend to give each symbol a random speed and create a random symbol each time one is 'destroyed'. Any clues where I can start?
As you seem new to flash as a platform I would think writing classes shouldn't be your first port of call when learning ActionScript. Definitely just play about on the timeline for now and learn the basics. As very simple solution to this, I would suggest creating a MovieClip in the library with a class name like 'MyBall'... then paste this onto the first frame of the main timeline et voila.
// Create some variables to store data
var numberOfBalls : int = 20;
var myBalls : Array = [];
var xVelocities : Array = [];
var maxXVelocitySpeed : Number = 5;
var minXVelocitySpeed : Number = 2;
// Add your orginal balls to the stage
for (var i : int = 0; i < numberOfBalls; i++)
{
var myBall : MyBall = new MyBall();
myBall.x = -(Math.random() * stage.stageWidth);
myBall.y = Math.random() * stage.stageHeight;
var xVelocity : Number = minXVelocitySpeed + (Math.random() * (maxXVelocitySpeed - minXVelocitySpeed));
myBalls.push(myBall);
xVelocities.push(xVelocity);
addChild(myBall);
}
// Add a listener for enter frame events
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
//Run this code on every frame to move the balls and reposition them if they are off the stage
function enterFrameHandler(event : Event) : void
{
for each( var myBall : MyBall in myBalls)
{
var ballIndex : int = myBalls.indexOf(myBall);
myBall.x += xVelocity[ballIndex];
if (myBall.x > stage.stageWidth)
{
myBall.x = -(Math.random() * stage.stageWidth);
myBall.y = Math.random() * stage.stageHeight;
}
}
}
First, turn your symbols into MovieClips. Then create a base class MySymbol.as for your symbols, something like:
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Point;
public class MySymbol extends MovieClip
{
public var speed:Number; // Pixels moved per frame
public function MySymbol(speed:Number, startPosition:Point)
{
this.speed = speed;
this.addEventListener(Event.ENTER_FRAME, update);
this.x = startPosition.x;
this.y = startPosition.y;
}
private function update():void
{
this.x -= this.speed;
if (this.x < 0 - this.width) { // We're at the left edge
this.removeEventListener(Event.ENTER_FRAME, update);
this.dispatchEvent(new Event(Event.COMPLETE));
}
}
}
}
Then make sure your movie clips are exported for AS3 (the "linkage" option on the item in the library). Make the class name for each item unique (e.g. MySymbol1, MySymbol2), and set the base class to MySymbol.
Your document class might look something like this:
package {
import flash.display.MovieClip;
import flash.events.Event;
import MySymbol; // Not strictly needed
public class DocumentClass extends flash.display.MovieClip
{
private static var SYMBOLS:Array = new Array(MySymbol1, MySymbol2);
public function DocumentClass()
{
// Create five symbols:
for (var i:int = 0; i < 5; i++) {
makeSymbol();
}
}
private function makeSymbol():void
{
// Pick a random symbol from the array:
var symType:Class = SYMBOLS[Math.random() * SYMBOLS.length];
// Construct the new symbol:
var loc:Point = new Point(stage.stageWidth, Math.random() * stage.stageHeight);
var sym:MySymbol = new symType(1 + Math.random() * 30, loc);
// Listen for the object hitting the left edge:
sym.addEventListener(Event.COMPLETE, remakeObject);
this.addChild(sym);
}
private function remakeObject(e:Event):void
{
e.target.removeEventListener(Event.COMPLETE, remakeObject);
this.removeChild(e.target);
// Replace the dead symbol:
makeSymbol();
}
}
}
It is a lot more efficient if instead of destroying and re-creating an object that flies off-stage you re-use the existing one and move it back to the right. But this is an optimization you can implement later, if things become slow.
Note that all the code above is UNTESTED and I have not coded AS3 in a while, so there's likely at least a few bugs in it. Hopefully it will serve as a good enough starting point.
Define a Circle (symbol) class that extends Sprite/Shape and has a velocity variable
Draw a circle (or whatever) with a random color
Math.floor(Math.random() * 0xffffff)
Assign a random value to velocity
minVelocity + Math.floor(Math.random() * velocityRange)
Create a start() method inside the Circle class that registers an enter frame handler
Increment this.y inside the enter frame handler, and dispatch a 'recycleMe' event if y is more than the max value.
Create N instances of Circle, addChild them, and call their start() methods.
listen to 'recycleMe' events on each of them, and reset the value of y from the handler.
Here's a few prompts to get you started.
MovieClips have an x and y property. If you were to add to these numbers over time you would see the MovieClip move along the x and/or y axis of the stage. Look into doing this using the Event.ENTER_FRAME which will allow you to change the values every time the screen is going to update.
Your stage will have a given width (a stageWidth property). You probably want to monitor when your MovieClip's x property is greater than the width of your stage. If it is remove (removeChild) it and add a new one (addChild) and place it back at the start x/y position.

Finding Something lighter than Sprites!

I am making a Sim City like game. There are lots of tiles. When I first started. I was just using a tilesheet. I was copying the necessary pieaces from the tilesheet. on to a blank bitMapData. I then took the bitMapData and put it into a bitMap which I then put into a DisplayObject. It worked great!
tileSheet:BitMapData <----- data is already in
loop { loop through and tiled
bg:bitMapData= new bitMapData();
bg.copyPixel(tileSheet,rect,point);
}
canvas.BitMap(bg);
addChild(canvas);
Only problem was I needed to make my tiles interactive. I needed to highlight them and change colors and stuff. So I used the Sprite object. It works great but I can only have so many on the stage at once. or else it moves slow when I scroll. I need something Lighter then a sprite, but yet I can still turn into a object to make interactive. Anyone have any ideas ???
If you have a lot of tiles, that will impact performance because Flash needs to update the transformations of a lot of display objects (which internally means a lot of matrix calculations, and subsequent redraws of big areas of the screen.)
There is another way to achieve interactivity, if you find that you must use a single bitmap data for performance. Keep an "abstract" (i.e. not graphical) data model in memory, that stores your game state. Make sure that you are able to read from your store where a certain element is positioned in the game world. Then you can use a flat bitmap data to render the game world, because the individual positions are stored elsewhere.
When the user clicks the DisplayObject containing the bitmap data (a Sprite in which the bitmap is drawn using a bitmap fill, or that wraps a Bitmap), look in your model which of your game elements was hit by that click.
// myTileSprite is a Sprite with a bitmap fill
myTileSprite.addEventListener(MouseEvent.CLICK, handleWorldClick);
function handleWorldClick(ev : MouseEvent) : void
{
var i : int;
// Loop through all game element data models
for (i=0; i<myGameElements.length; i++) {
// Test the mouse position against the element model
if (myGameElements[i].hitTest(myTileSprite.mouseX, myTileSprite.mouseY)) {
trace('this was the element that was clicked: '+myGameElements[i].toString());
}
}
}
Here, whenever the player clicks the world graphics, the loop tries to find that element which was directly under the mouse position. You will need to implement a hitTest() method on all your game element data models, of course. Such a method simply checks the supplied world space position against the tile's area:
// GameElement.hitTest():
/**
* Tests a world position against the position and area of this game
* element tile. Returns a boolean indicating whether this tile was hit.
*/
public function hitTest(mouseX : Number, mouseY : Number) : void
{
var rect : Rectangle = new Rectangle(this.worldX, this.worldY, this.width, this.height);
if (mouseX > rect.left && mouseX < rect.right
&& mouseY > rect.top && mouseY < rect.top) {
return true;
}
else return false;
}
The GameElement class is not an display object, but has worldX and worldY properties indicating where it is located in the world. It's width and height properties define it's dimensions.
The trick from hereon is to make sure that the rendered bitmap and your model storage is synchronized, so that a tile's position on the bitmap really corresponds to it's worldX/worldY properties in the data model.
I am one step ahead of you. And that is a great idea. Its alot easier to keep a data representation of the world when the tiles are squared. I therefore can take my mouseX/tileWidth, and thats hw many columns I moved from left to right. same with the Y axis.
Not only that but coordinates start at top left corner.
But issue I have is that my tiles are Isometric. So instead of the X axis start off like...
012345678
0
1
2
3
4
5
6
7
8
My tiles are aligned like...
00
1 1
2 2
3 3
4 4
5 6
its a little sloppy. but the right side represents the y axis and the left represents the x axis. and the center origin is in the center of the screen. not on the top left. I am trying to figure out how to measure where my mouse is from the center and out on both sides. This sounds extremely difficult. I am not sure if its possible. The game is suppose to be like a sim city like game. The first sim city was squares not isometric. I dont think they went isometric until they started using 3d. I wonder if its possible to create a illusion of isometric on a square tile.
Ive been reading this great book on isometrics. They show to calculate tiles in 3d space. and even calculate your mouse in 3d space as well. here is the code. Its alot, but I hope someone else understands it more then I. The book was written by jobe makar on building multiplayer worlds. I wanted to share it because the code it is pretty simple as far as amount of code put into it. only 2 classes needed. I am not that good with trigonometry. so I cant really interpret how the math is getting the results. hopefully someone can explain that for me :D.
Y coordinates are not given because the width is = to height. The coordinates method is just a custom made Point class which holds x, y and z.
package com.gamebook.grid {
import com.gamebook.utils.geom.Coordinate;
import com.gamebook.utils.Isometric;
import flash.display.MovieClip;
import flash.events.MouseEvent;
/**
* ...
* #author Jobe Makar - jobe#electrotank.com
*/
public class Map extends MovieClip{
private var _grid:Array;
private var _iso:Isometric;
private var _tileWidthOnScreen:int;
private var _tileHeightOnScreen:int;
private var _tileWidth:Number;
private var _tileHeight:Number;
private var _cols:int;
private var _rows:int;
private var _lastTile:Tile;
public function Map() {
initialize();
}
private function initialize():void{
_iso = new Isometric();
//when mapped to the screen the tile makes a diamond of these dimensions
_tileWidthOnScreen = 64;
_tileHeightOnScreen = 32;
//figure out the width of the tile in 3D space
_tileWidth = _iso.mapToIsoWorld(64, 0).x;
//the tile is a square in 3D space so the height matches the width
_tileHeight = _tileWidth;
buildGrid();
addEventListener(MouseEvent.MOUSE_MOVE, mouseMoved);
}
private function mouseMoved(e:MouseEvent):void {
if (_lastTile != null) {
_lastTile.alpha = 1;
_lastTile = null;
}
var coord:Coordinate = _iso.mapToIsoWorld(mouseX, mouseY);
var col:int = Math.floor(coord.x / _tileWidth);
var row:int = Math.floor(Math.abs(coord.z / _tileHeight));
if (col < _cols && row < _rows) {
var tile:Tile = getTile(col, row);
tile.alpha = .5;
_lastTile = tile;
}
}
private function buildGrid():void{
_grid = [];
_cols = 10;
_rows = 10;
for (var i:int = 0; i < _cols;++i) {
_grid[i] = [];
for (var j:int = 0; j < _rows;++j) {
var t:Tile = new Tile();
var tx:Number = i * _tileWidth;
var tz:Number = -j * _tileHeight;
var coord:Coordinate = _iso.mapToScreen(tx, 0, tz);
t.x = coord.x;
t.y = coord.y;
_grid[i][j] = t;
addChild(t);
}
}
}
private function getTile(col:int, row:int):Tile {
return _grid[col][row];
}
}
}
Then we have the isometric class that calculates 3d space.
package com.gamebook.utils {
import com.gamebook.utils.geom.Coordinate;
/**
* #author Jobe Makar - jobe#electrotank.com
*/
public class Isometric {
//trigonometric values stored for later use
private var _sinTheta:Number;
private var _cosTheta:Number;
private var _sinAlpha:Number;
private var _cosAlpha:Number;
/**
* Isometric class contrustor.
* #param declination value. Defaults to the most common value, which is 30.
*/
public function Isometric() {
var theta:Number = 30;//even though the tiles are already isometric, you still have to put the degrees the tiles will be turned.
var alpha:Number = 45;//45 degrees on y axis, 30 dgrees on x axis
theta *= Math.PI/180; // then you translate to radians
alpha *= Math.PI/180;
_sinTheta = Math.sin(theta);
_cosTheta = Math.cos(theta);
_sinAlpha = Math.sin(alpha);
_cosAlpha = Math.cos(alpha);
}
/**
* Maps 3D coordinates to the 2D screen
* #param x coordinate
* #param y coordinate
* #param z coordinate
* #return Coordinate instance containig screen x and screen y
*/
public function mapToScreen(xpp:Number, ypp:Number, zpp:Number):Coordinate {
var yp:Number = ypp;
var xp:Number = xpp*_cosAlpha+zpp*_sinAlpha;
var zp:Number = zpp*_cosAlpha-xpp*_sinAlpha;
var x:Number = xp;
var y:Number = yp*_cosTheta-zp*_sinTheta;
return new Coordinate(x, y, 0);
}
/**
* Maps 2D screen coordinates into 3D coordinates. It is assumed that the target 3D y coordinate is 0.
* #param screen x coordinate
* #param screen y coordinate
* #return Coordinate instance containig 3D x, y, and z
*/
public function mapToIsoWorld(screenX:Number, screenY:Number):Coordinate {
var z:Number = (screenX/_cosAlpha-screenY/(_sinAlpha*_sinTheta))*(1/(_cosAlpha/_sinAlpha+_sinAlpha/_cosAlpha));
var x:Number = (1/_cosAlpha)*(screenX-z*_sinAlpha);
return new Coordinate(x, 0, z);
}
}
}