Flash AS3: Error with stop button for game? - actionscript-3

I am making a small game from a tutorial where you hit small tofu's with your cursor and gather up points and accuracy - I have implemented a start button that works and goes to frame(2), where the game starts.
I would like to create a stop button but when i make a button in the scene - the button does not function (cant click on it) and when i hit a tofu it gets the 1009 error? (Cannot access a property or method of a null object reference.)
The game works when i have no stop button but i can't exit the game.
How can i create a stop button or a menu inside the game that allows the user to go back to a previous scene or stop the game?
// define some global variables which let you track the player's statistics.
var hits:int = 0;
var misses:int = 0;
var shots:int = 0;
var cDepth:int = 100;
var level:int = 1;
// define some runtime variables which are used in calculations.
var xSpeed:Number = 3;
var stageWidth:Number = 480;
var stageHeight:Number = 580;
/* attach the crosshair_mc movie clip instance from the Library onto the Stage.
This clip is used as a custom mouse cursor. */
var crosshairClip:MovieClip = new crosshair_mc();
crosshairClip.mouseEnabled = false;
addChild(crosshairClip);
// hide the mouse cursor
Mouse.hide();
/* every time the mouse cursor moves within the SWF file,
update the position of the crosshair movie clip instance on the Stage. */
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
function mouseMoveHandler(event:MouseEvent):void {
crosshairClip.x = event.stageX;
crosshairClip.y = event.stageY;
};
/* when the mouse button is clicked, check to see if the cursor is within the boundaries of the Stage.
If so, increment the number of shots taken. */
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
function mouseDownHandler(event:MouseEvent):void {
if (bg_mc.hitTestPoint(event.stageX, event.stageY, false)) {
shots++;
updateStats();
}
};
// define a TextFormat which is used to format the stats_txt text field.
var my_fmt:TextFormat = new TextFormat();
my_fmt.bold = true;
my_fmt.font = "Arial";
my_fmt.size = 12;
my_fmt.color = 0xFFFFFF;
// create a text field to display the player's statistics.
var stats_txt:TextField = new TextField();
stats_txt.x = 10;
stats_txt.y = 0;
stats_txt.width = 530;
stats_txt.height = 22;
addChild(stats_txt);
// apply the TextFormat to the text field.
stats_txt.defaultTextFormat = my_fmt;
stats_txt.selectable = false;
updateStats();
// add an onEnterFrame event to the main timeline so new tofu is constantly added to the game.
stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
function enterFrameHandler(event:Event):void {
// randomly add new target's to the Stage.
if (randRange(0, 20) == 0) {
var thisMC:MovieClip;
// attach a new instance of the tofu instance from the library onto the Stage, and give it a unique depth.
var randomTofu:Number = randRange(1, 3);
switch (randomTofu) {
case 1:
thisMC = new tofu1_mc();
break;
case 2:
thisMC = new tofu2_mc();
break;
case 3:
thisMC = new tofu3_mc();
break;
default:
return;
break;
}
cDepth++;
// set the starting postition of the current target movie clip so it is just off to the left of the Stage.
thisMC.x = -thisMC.width;
/* create a random number between 80 and 100.
This is used to set the current movie clip's scale,
alpha and speed that it moves across the Stage. */
var scale:int = randRange(80, 100);
/* set the _xscale and _yscale properties of the current movie clip.
This allows for some minor variations of the targets within the game. */
thisMC.scaleX = scale / 100;
thisMC.scaleY = scale / 100;
thisMC.alpha = scale / 100;
thisMC.speed = xSpeed + randRange(0, 3) + level;
/* set a random _y value for the target.
Now, instead of all targets flying along the same path,
they vary their vertical position slightly. */
thisMC.y = Math.round(Math.random() * 350) + 65;
thisMC.name = "tofu" + cDepth;
/* create an onEnterFrame handler that executes a couple dozen times per second.
Update the target's position on the Stage. */
thisMC.addEventListener(Event.ENTER_FRAME, tofuEnterFrameHandler);
thisMC.addEventListener(MouseEvent.CLICK, tofuClickHandler);
addChild(thisMC);
// swap the custom cursor to the higher depth
swapChildren(thisMC, crosshairClip);
}
};
/* create a function to update the player's statistics on the Stage.
You're displaying number of shots taken, number of targets "hit",
number of targets "missed", the percentage of hits vs misses,
overall accuracy (number of shots taken vs number of hit targets). */
function updateStats() {
var targetsHit:Number = Math.round(hits/(hits+misses)*100);
var accuracy:Number = Math.round((hits/shots)*100);
if (isNaN(targetsHit)) {
targetsHit = 0;
}
if (isNaN(accuracy)) {
accuracy = 0;
}
stats_txt.text = "shots:"+shots+"\t"+"hits: "+hits+"\t"+"misses: "+misses+"\t"+"targets hit: "+targetsHit+"%"+"\t"+"accuracy: "+accuracy+"%"+"\t"+"level:"+level;
}
/* create a function that returns a random integer between two specified numbers.
This allows you to add some subtle differences in size and speed for the movie clips on the Stage. */
function randRange(minNum:Number, maxNum:Number):Number {
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
function tofuEnterFrameHandler(event:Event):void {
var tofuMC:MovieClip = event.currentTarget as MovieClip;
/* move the target horizontally along the Stage.
Currently all targets will move from left to right. */
tofuMC.x += tofuMC.speed;
/* slightly decrement the _y position of the current target movie clip.
This makes it appear like the targets are flying slightly higher as they move across the Stage. */
tofuMC.y -= 0.4;
/* if the current position of the target is no longer on the Stage,
count the target as a "miss" and delete the instance.
If the instance wasn't deleted from the Stage,
the user's computer would eventually slow to a crawl. */
if (tofuMC.x > stageWidth) {
misses++;
updateStats();
removeChild(tofuMC);
tofuMC.removeEventListener(Event.ENTER_FRAME, tofuEnterFrameHandler);
}
}
// when the target movie clip instance is pressed, count it as a "hit".
function tofuClickHandler(event:MouseEvent):void {
var tofuMC:MovieClip = event.currentTarget as MovieClip
// update the player's stats
hits++;
if ((hits%40) == 0) {
level++;
}
updateStats();
/* go to the movie clip's label named "hit"
(which allows you to show a clever animation when the instance is hit.) */
tofuMC.gotoAndPlay("hit");
// create an onEnterFrame event for the current movie clip instance.
tofuMC.addEventListener(Event.ENTER_FRAME, tofuHitEnterFrameHandler);
/* delete the onPress event handler.
This makes it so the target cannot continually be clicked while it is falling from the sky. */
tofuMC.removeEventListener(MouseEvent.CLICK, tofuClickHandler);
tofuMC.removeEventListener(Event.ENTER_FRAME, tofuEnterFrameHandler);
}
function tofuHitEnterFrameHandler(event:Event):void {
var tofuMC:MovieClip = event.currentTarget as MovieClip;
// set some local variables that you'll use to animate the target falling from the sky.
var gravity:int = 20;
var ymov:int = tofuMC.y + gravity;
// ***** xmov *= 0.5;
// increment the rotation of the current movie clip clock-wise by 5 degrees.
tofuMC.rotation += 5;
/* set the _x and _y properties of the movie clip on the Stage,
this allows us to make the target look like it is semi-realistically
falling from the sky instead of just dropping straight down. */
tofuMC.x += xSpeed;
tofuMC.y = ymov;
/* after the _y position is off of the Stage,
remove the movie clip so that the coordinates aren't continually calculated */
if (tofuMC.y > stageHeight) {
removeChild(tofuMC);
tofuMC.removeEventListener(Event.ENTER_FRAME, tofuHitEnterFrameHandler);
}
}

The stage is confused, because some objects don't exist when you change frames, so you have to remove all of your events from the stage:
stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
And then you can call the gotoAndStop() function to whatever frame you want...

Related

How to apply drag and drop function to all frames in movie clip? Actionscript3

Each frame contain 1 text field. I apply the code on timeline.
But it only gets applied to the last object, which means that I can only drag and drop the last object. Why?
How can I improve this so that I can drag and drop all objects?
for(var j:uint=0; j<3; j++)
{
var q:Ans = new Ans();
q.stop();
q.x = j * 300+50;// set position
q.y = 500;
var r:uint = Math.floor(Math.random() * q_list.length);
q.qface = q_list[r];// assign face to card
q_list.splice(r,1);// remove face from list;
q.gotoAndStop(q.qface+1);
q.addEventListener(MouseEvent.MOUSE_DOWN, startAnsDrag);
q.addEventListener(MouseEvent.MOUSE_UP, stopAnsDrag);
q.addEventListener(Event.ENTER_FRAME, dragAns);
addChild(q);// show the card
}
//----------------------------drag
// offset between sprite location and click
var clickOffset:Point = null;
// user clicked
function startAnsDrag(event:MouseEvent) :void
{
clickOffset = new Point(event.localX, event.localY);
}
// user released
function stopAnsDrag(event:MouseEvent) :void
{
clickOffset = null;
}
// run every frame
function dragAns(event:Event) :void
{
if (clickOffset != null)
{ // must be dragging
q.x = clickOffset.x+mouseX+135;
q.y = clickOffset.y+mouseY;
}
}
Make a new layer in the timeline just for your drag-and-drop code, which you can remove from your other actionscript. Put the code on the first frame in that layer. Now click on and select the last frame on that layer in which you want the code to be effective (probably the last frame of the MovieClip). Press F5 to draw-out the range of frames which will be affected by the code. Voila!

changing a target crosshair color in actionscript 3

I am stuck at a very basic problem which I am not quite able to figure out. I have 4 alienships and a crosshair which moves around with the mouse. I want to change the color of my crosshair to red when it is near one of the alienships. Both my alienships and crosshair are classes which extend movieclip. I use them in my main document class inside a main game loop which is triggered by the Enter_Frame event listener.
Here is my logic that checks for the same
private var objColor:ColorTransform = new ColorTransform();
private function overlayCursorMove() : void {
var initialColor:ColorTransform = new ColorTransform();
initialColor.color = 0xD3D3D3;
for(var i = 0; i < alienShipArray.length; i++){
var currentShip:AlienShip = alienShipArray[i];
if(currentShip.getDistance(overlayCursor.x - currentShip.x, overlayCursor.y - currentShip.y) <= 30 ){
overlayCursor.transform.colorTransform = objColor;
}
else if(currentShip.getDistance(overlayCursor.x - currentShip.x, overlayCursor.y - currentShip.y) > 30 ){
overlayCursor.transform.colorTransform = initialColor;
}
}
}
Below is the custom getDistance function inside the AlienShip class
public function getDistance(delta_x:Number, delta_y:Number):Number
{
return Math.sqrt((delta_x*delta_x)+(delta_y*delta_y));
}
overlayCursor is my crosshair which moves around with my mouseX and mouseY positions. I have 4 alienships on my stage and all of them are stored inside the alienShipArray.
I am just calculating the distance between my crosshair and the currentship position to check if its less than a certain amount to change the crosshair color else if distance is greater change it back to previous color.
Surprisingly the logic only works for the very last ship in the array that is when the loop reaches i = 3 ( alienShipArray[3] ). For the rest of the ship the color of the crosshair stays the same!
You need to break out of your loop when you encounter an intersection, otherwise your loop will continue to run and the other non-intersecting aliens will cause the else if part of your condition to test true, and your crosshair will be changed back to its initial color.
That would explain why your code only works with the last alien in the loop, as there are no further iterations in that instance to reset the color.
Something like the following should do it (untested):
private var objColor:ColorTransform = new ColorTransform();
private function overlayCursorMove() : void {
var initialColor:ColorTransform = new ColorTransform();
initialColor.color = 0xD3D3D3;
for(var i:uint = 0; i < alienShipArray.length; i++) {
var currentShip:AlienShip = alienShipArray[i];
// Reset cursor back to initial color
overlayCursor.transform.colorTransform = initialColor;
if(currentShip.getDistance(overlayCursor.x - currentShip.x, overlayCursor.y - currentShip.y) <= 30 ) {
// Intersection found, change the cursor color
overlayCursor.transform.colorTransform = objColor;
// And break out of the loop so further iterations don't reset it
break;
}
}
// No intersections found, cursor remains in its initial state
}

How to slowly move item up and down?

I need to do simple thing, by clicking button 1 time, slowly move item1 by 100 px up and move down by 100 px. I've tried this, but item1 immediately increasing by 50px and immediately decreasing by 50px, I need to make It slower.
var moving:Boolean = false;
if(!moving){
item1.y -= 50;
moving = true;
}
else {
item1.y += 50;
moving = false;
}
You could set up a max moving value like 50, and then move the item1 on the Y with 1, and decrease that max value by 1. When the max value reaches 0, the item reached it's destiny.
if(!moving){
item1.y-=1;
maxValue--;
if(maxValue==0){
//reached final position
}
}
Use a Timer object. Declare that object inside of your class. Then, when the button has been clicked, set the object to a new instance of a Timer that will run very quickly and for many iterations, add an event listener to your new Timer, and in the event listener, apply much smaller increments or decrements to item1.y. For example:
private var m_tmr:Timer;
private function buttonClickHandler(pEvent:MouseEvent):void
{
// This is the function that's called when the button's clicked.
if (m_tmr == null)
{
m_tmr = new Timer(200, 0);
m_tmr.addEventListener(TimerEvent.TIMER, onTimer);
m_tmr.start();
}
}
private function onTimer(pEvent:TimerEvent):void
{
// The first several times this function is called (should be around every
// 200 milliseconds), increment item1.y by 1 or 2 or something else small.
// After the first many times, start decrementing item1.y by the same amount.
// Then call m_tmr.removeEventListener(TimerEvent.TIMER, onTimer);
}
You have to tell Flash to re-draw the screen after each incremental move. An excellent way to do this is with an ENTER_FRAME loop:
var moving:Boolean = false;
const initY = item1.y; // your starting y value
const limitY = initY - 100; // your move will end here
if(!moving){
moving = true;
addEventListener(Event.ENTER_FRAME,moveit)
function moveit(e)
{
item1.y -=1
if (item1.y < limitY)
removeEventListener(Event.ENTER_FRAME,moveit)
}
you have to removeEventListener(...) once you've got to where you want to be, otherwise the loop will go on and hog memory and performance.
UPDATE
So, to move up on a mouse click, you'd do this:
var moving:Boolean = false;
const initY = item1.y; // your starting y value
const limitY = initY - 100; // your move will end here
stage.addEventListener(MouseEvent.CLICK, moveUp)
function moveUp(e)
{
stage.removeEventListener(MouseEvent.CLICK, moveUp)
if(!moving){
moving = true;
addEventListener(Event.ENTER_FRAME,moveit)
function moveit(e)
{
item1.y -=1;
if (item1.y < limitY)
{
removeEventListener(Event.ENTER_FRAME,moveit);
item1.y = limitY;
moving = false;
}
}
}
Instead of targeting stage you may just want to target your button when you use the addEventListener method to register the listener function with the mouse click.
To move back to the start position, apply the same idea to another button or another MouseEvent. For instance you could move up on MOUSE_DOWN and move down on MOUSE_UP.
There are more sophisticated things you can do inside the listener functions (in this case the moving functions). You could apply "easing" to the beginning and ending of the moves so that the motion seems more natural. But, you'll have to read up on that - this answer is too long already!
I would like participate in this conversation. My version of object movement without If statements. Movement is based on trigonometric function:
var objectToAnimate:Shape = new Shape();
objectToAnimate.graphics.beginFill(0x009900);
objectToAnimate.graphics.drawCircle(0, 0, 20);
addChild(objectToAnimate);
//Place it somewhere
objectToAnimate.x = objectToAnimate.y = 200;
//Config for movement
var step:Number = 1; //really slow... 1° per frame
var maxOffsetY:Number = -100; //Move object maximum on 100px top
var cursor:Number = -90;
var position: Number = objectToAnimate.y; // catch current position
var timer:Timer = new Timer(30, 180);
timer.addEventListener(TimerEvent.TIMER, updateAnimation);
timer.start();
function updateAnimation(e:TimerEvent):void {
objectToAnimate.y = position + Math.cos(cursor * Math.PI / 180) * maxOffsetY;
cursor += step;
}

Enemy move randomly

To make things quick, I have an arrangement of tiles that a player and an enemy are on.
public static var floor1:Array = new Array(7);
floor1[0] = [0,1,1,1,1,1,0];
floor1[1] = [1,1,1,1,1,1,1];
floor1[2] = [1,1,1,0,1,1,1];
floor1[3] = [1,1,0,0,0,1,1];
floor1[4] = [1,1,1,0,1,1,1];
floor1[5] = [1,1,1,1,1,1,1];
floor1[6] = [0,1,1,1,1,1,0];
public function Main()
{
var tilew:int = 60;
var tileh:int = 60;
for (var i:int=0; i<floor1.length; i++)
{
for (var u:int=0; u<floor1[i].length; u++)
{
var cell:MovieClip = new Tile();
cell.gotoAndStop(floor1[i][u]);
cell.x = ((u-i)*tileh);
cell.y = ((u+i)*tilew/2);
addChild(cell);
cell.addEventListener(MouseEvent.ROLL_OVER, mouseover);
cell.addEventListener(MouseEvent.ROLL_OUT, mouseout);
cell.addEventListener(MouseEvent.CLICK, mouseclick);
cell.addEventListener(Event.ENTER_FRAME, beginfloor1);
}
}
var player:Player = new Player();
addChild(player);
player.mouseEnabled = false;
player.x = 5 * (tileh);
player.y = 5 * (tilew/2);
var enemy:Enemy = new Enemy();
addChild(enemy);
enemy.mouseEnabled = false;
enemy.x = 9 * (tileh);
enemy.y = 9 * (tileh/2);
My goal is to have the enemy move randomly on tiles in his range. What I did was create a square graphic called enemyVisionArea that checks which tile is hitting the enemy, which is basically surrounding tiles.
I have a timer function that tells the enemy to move every 5 seconds if the player isn't near him and if he's next to an available tile.
function timerenemy (event:TimerEvent){
if (enemy.enemyVisionArea.hitTestObject(enemyMover) && !player.visionPoint.hitTestObject(enemyMover.tileMiddle))
{
enemy.x = (enemyMover.x)+55;
enemy.y = (enemyMover.y)+20;
trace("moved");
}
}
enemyMover is a variable that I made equal to the tile objects.
function beginfloor1(event:Event)
{
enemyMover = event.currentTarget as Tile;
}
It just stays where it is. I'm just want to have the enemy move on its own on any tile that its enemyVisionArea is hitTesting a nearby tile. The beginfloor1 function doesn't seem to be working. Is there any way I can declare enemyMover = event.currentTarget as Tile and have the enemy move on a random tile that its enemyVisionArea is hitTesting?
If this is confusing, I can post the full code.
You are assigning 49 enterframe listeners which are called in sequence, and they ALL change one single variable to the cell they are attached to. Of course, the last tile is what's always assigned.
I expect that you want an enemy to check if there's a tile available for it to move to. You are essentially checking for one tile which is enemyMover - how do you determine what's that tile? You have to check all available tiles that are around the enemy, make a list of them and select one out of that list that's not the current tile, then move the enemy there.
So, first you need a complete tileset to be addressable from somewhere. The best way will be to declare a class-wide var tileset:Array and fill it where you make new tiles. Drop the Event.ENTER_FRAME listener from the code there, as it's useless. Then, in your timerevent that's for the enemy you do check all of the tileset if they are within your enemy's vision area (you use hitTestObject, I'd use clear distance grid-wise or coordinate-wise - it's a whole lot faster), if so, you add them to the TEMPORARY array you create within that function. Of course, if your enemy is at the currently processed cell, you ignore it - you have to move your enemy, not make him stand in place. Then, select (somehow, it's up to you) what cell your enemy should move to, and execute a move. Yes, if you want your enemy to move randomly, select a cell at random by its index via Math.floor(Math.random()*selectedcells.length).

While loop not working in ActionScript 3.0

I'm trying to create a function (called by a mouseover event) which will play the movie backwards while the mouse is over the instance, and stop while it's not.
This is what I have so far:
var b2:rightButton = new rightButton(); //new instance
b2.X = 550; //instances position
addChild(b2); //add instance to stage
b2.alpha = .4; // set instances alpha
var num = 0; // new variable called 'num'
b2.addEventListener(MouseEvent.ROLL_OVER, rightScroll); //mouse event for roll over
function rightScroll(event:MouseEvent) { //the function
num = 1; //set num to 1
b2.alpha = .8; //set alpha to 80%
}
b2.addEventListener(MouseEvent.ROLL_OUT, no_rightScroll); //event for roll out
function no_rightScroll(event:MouseEvent){ //roll- out function
num = 0; //set num back to 0
b2.alpha = .4; //set alpha back to 40%
}
while (num == 1){ // while num =1 (while mouse is over)
prevFrame(); //goto previous frame
}
Does anyone knows how to fix that, or a better way to do this?
The problem with your code at the moment is that loops do not execute in line with the framerate of your movie, so your code will call prevFrame() many, many times in a single frame. This can result in endless loops, which will cause your program to crash, and is completely useless in terms of animation.
The best approach is to listen to the ENTER_FRAME event of your movie, and move the playhead back one frame each time the event is dispatched. By adding and removing the listener in your button actions, you will get the effect you're looking for:
var b2:rightButton = new rightButton(); //new instance
b2.X = 550; //instances position
addChild(b2); //add instance to stage
b2.alpha = .4; // set instances alpha
b2.addEventListener(MouseEvent.ROLL_OVER, rightScroll); //mouse event for roll over
function rightScroll(event:MouseEvent):void { //the function
stage.addEventListener(Event.ENTER_FRAME,moveBackwards); //add stage listener
b2.alpha = .8; //set alpha to 80%
}
b2.addEventListener(MouseEvent.ROLL_OUT, no_rightScroll); //event for roll out
function no_rightScroll(event:MouseEvent):void { //roll- out function
stage.removeEventListener(Event.ENTER_FRAME,moveBackwards); //remove stage listener
b2.alpha = .4; //set alpha back to 40%
}
function moveBackwards(evt:Event):void {
prevFrame();
}
You'd probably find it much easier to write and read if you use the Greensock TweenLite library. Tweenlite can tween frames just as easily as other numeric values.
Inside your event listener, try adding this:
TweenLite.to( targetMC, 1, { frame:1, ease:fl.transitions.easing.None.easeNone } );
It will tween your movieclip (targetMC in this example) to frame 1 linearly with a duration of 1 second. You can play around with it using any of the other Tweenlite parameters as well.