AS3 - Error #1180: Call to a possibly undefined method kill. - actionscript-3

This one is driving me crazy for a couple hours. I try to call a method kill(); (in function takeDamage()) which is in the same class, yet it won't find it.
package classes.ship
{
imports ...
public class Ship extends MovieClip
{
var speed:Number;
var shootLimiter:Number;
public static var health:Number;
public static var maxHealth:Number;
public function initialize()
{
var stageReff:Stage = this.stage as Stage;
stage.addEventListener(KeyboardEvent.KEY_DOWN, reportKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, reportKeyUp);
stage.addEventListener("enterFrame", move);
}
//code
public static function takeDamage(d):void
{
health -= d;
if(health <= 0)
{
health = 0;
kill();
}
Main.healthMeter.bar.scaleX = health/maxHealth;
}
public function kill():void
{
var boom = new Explosion();
stage.addChild(boom);
boom.x = this.x;
boom.y = this.y;
this.visible = false;
//Main.gameOver();
}
//code
}
}
Has it to do with var stageReff:Stage = this.stage as Stage; ?
Thanks in advance.

kill() is an instance method, but takeDamage is a static class method. You can't call instance methods from a static class method. You can only call instance methods when you have an instance reference to call it on.

nice simple one for early in the year!
You have declared the function 'takeDamage' as a static method - this means that it does not belong to a particular instance of the class Ship, instead it belongs to the Class itself. Static methods and properties can be a bit confusing if you are new to OOP, but are easily explained through a quick example:
Class Member Property
In this example we declare a new Class definition for a Ship where we can define the speed of the ship instance via setSpeed().
public class Ship {
private var speed : Number;
public function setSpeed(value : Number) : void {
this.speed = value;
}
public function getSpeed() : Number {
return this.speed;
}
}
Now we will create a couple of ships and set their speed:
var scoutShip : Ship = new Ship();
scoutShip.setSpeed(500); // Scout's are fast!
var cargoShip : Ship = new Ship();
cargoShip.setSpeed(10); // Cargo ships are sloooow.
trace("Scout Ship Speed: " + scoutShip.getSpeed()); // 500
trace("Cargo Ship Speed: " + cargoShip.getSpeed()); // 10
As you can see from the above, each new instance of Ship that we create can have its own Speed - this is a fundamental of Object Orientated Programming (where the Ship is the Object and it's speed is the data).
Static Property
Now we will create another class, this time called StaticShip which uses a static property instead, note the use of the static keyword:
public class StaticShip {
private static var speed : Number;
public function setSpeed(value : Number) : void {
this.speed = value;
}
public function getSpeed() : Number {
return this.speed;
}
}
Because the speed property is static it is shared across all instances of StaticShip; for example:
var scoutShip : StaticShip = new StaticShip();
scoutShip.setSpeed(500); // So the scout should move at 500
var cargoShip : StaticShip = new StaticShip();
cargoShip.setSpeed(10); // ... and the Cargo move at 10, as before
trace("Scout Ship Speed: " + scoutShip.getSpeed()); // 10
trace("Cargo Ship Speed: " + cargoShip.getSpeed()); // 10
Notice how both StaticShips move at 10 - this is because we set the Speed of the 'cargoShip' instance last - as the 'speed' property in StaticShip is declared static it is shared across all instances of that Class.
Now, just as you can have static properties in Classes, you can also have static functions. Usually, when you call a Class' method (ie: setSpeed()) you need to invoke that method on an instance (ie: scoutShip.setSpeed(500);), however, Static Methods allow you to interact with other static members of a given class, here's another example:
Static Method Example
public class StaticMethodShip {
private static var speed : Number;
// Note that setSpeed is now declared as static
public static function setSpeed(value : Number) : void {
this.speed = value;
}
public function getSpeed() : Number {
return this.speed;
}
}
Now, we can still create new instances of StaticMethodShip as before, but because we have now declared 'setSpeed' as static, we can't invoke setSpeed on an instance:
var scoutShip : StaticMethodShip = new StaticMethodShip();
// This call will trigger Error #1180 - Call to a possibly undefined Method because
// setSpeed was declared as static.
scoutShip.setSpeed(500);
Instead, we can now only invoke the setSpeed() method on the StaticMethodShip Class, ie:
// Set the speed of all StaticMethodShip instances.
StaticMethodShip.setSpeed(250); // all StaticMethodShips travel at 250.
// Proof!
var shipOne : StaticMethodShip = new StaticMethodShip();
var shipTwo : StaticMethodShip = new StaticMethodShip();
trace("ShipOne Speed: " + shipOne.getSpeed()); // 250
trace("ShipTwo Speed: " + shipTwo.getSpeed()); // 250
Static methods are useful when you want to define behaviour for all instances of a given Class (ie: all StaticMethodShips move at the specified speed, all fade out Tweens last for 0.25 seconds, etc); but they are also used in common design Patterns such as the Static Factory Method
Now, to the reason you are seeing your error - member level methods are able to invoke static methods, ie:
public class StaticExampleOne {
public static function getName() : String {
return "Robbe";
}
public function traceName() : void {
// traces 'Robbe'.
trace(getName());
}
}
In usage (new StaticExampleOne().traceName()) this works just fine - member methods can access static methods without problem, however this doesn't work the other way around:
public class StaticExampleTwo {
private var name : String = "Robbe";
public function getName() : void {
return this.name;
}
public static function traceName() : void {
// Throws Error #1180.
trace(getName());
}
}
This is because static methods have no scope (ie: They do not know which instance of the Class they are referring too because they can only reference other static members) and therefore can not access class level members (methods and properties).
To solve your problem you could introduce a new static property to Ship called 'STAGE' (typically static properties are written in ALL CAPS to differentiate them from member properties) and then make your kill() method static.
Hope this helps and good luck!
Jonny.

Related

Get stage in ActionScript-3 without a DisplayObject?

How can I get a reference to my stage without having a Sprite/DisplayObject that is added to the stage already ?
More info: I have a static class that is a utility class and I want it to initialize in static class constructor but I also need the reference to the stage.
public class UtilClass
{
trace("init: " + stage);
}
First thing that is called in my AS-3 apps is the constructor of my main Sprite/DisplayObject and it has access to the stage. So the stage exists at that point. Then I call utility methods of my UtilClass. Now I want it to initialize by itself on the first use (when stage is already in existance).
I want to know if stage object can be accessed from anywhere without being initialized from outside of the utility class.
Edit:
public class SimpleSprite extends Sprite
{
public static var aaa:int = 12;
public static function test():void
{
trace("here I am");
}
trace(aaa, Capabilities.screenResolutionX+", "+Capabilities.screenResolutionY);
test();
}
The stage reference is available in your MainTimeline or Main instance, depending on platform. You can add code there to pass that reference to other classes should you need it. The class should have a method (static, in your case) that'll accept a Stage parameter and store it somewhere inside the class.
public class UtilClass {
private static var theStage:Stage=null;
public static function initialize(s:Stage):void {
if (theStage) return; // we're initialized already
theStage=s;
}
// once you call this, you can "trace(theStage)" and get correct output
// other methods can also rely on theStage now.
}
Then you call UtilClass.initialize(stage); and you're set.
You will need to initialise your UtilClass and pass the stage reference. I recommend you to have a Class only for 'manage' Stage reference.
You could try something like this (just a quick example):
public class StageReference
{
public static const STAGE_DEFAULT:String = 'stageDefault';
protected static var _stageMap:Dictionary;
public static function getStage(id:String = StageReference.STAGE_DEFAULT):Stage
{
if (!(id in StageReference._getMap()))
throw new Error('Cannot get Stage ("' + id + '") before it has been set.');
return StageReference._getMap()[id];
}
public static function setStage(stage:Stage, id:String = StageReference.STAGE_DEFAULT):void
{
StageReference._getMap()[id] = stage;
}
public static function removeStage(id:String = StageReference.STAGE_DEFAULT):Boolean
{
if (!(id in StageReference._getMap()))
return false;
StageReference.setStage(null, id);
return true;
}
protected static function _getMap():Dictionary
{
if (!StageReference._stageMap) StageReference._stageMap = new Dictionary();
return StageReference._stageMap;
}
}
When you start your application (Main Class or where you start to include your logic)
StageReference.setStage(stage);
And when you need to get the stage reference
trace('Checking the Stage: ', StageReference.getStage());

Saving static instance of Main - Actionscript 3

So I need to access information from my document(Main.as) class.
I tried making this easy by saving a static instance of my Main class.
private static var _instance:Main;
public static function get instance():Main { return _instance; }
public function Main() {
_instance = this;
}
Then when in another class I try to use this I get a null reference error.
public function InputController():void {
main = Main.instance;
main.stage.addEventListener(KeyboardEvent.KEY_DOWN, OnKeyPress);
main.stage.addEventListener(KeyboardEvent.KEY_UP, OnKeyRelease);
}
I get an error on the main.stage.addEventListener lines.
If you have timeline coding, you can instantiate your static variable with _instance=this (should be accessible, as main timeline is a part of document class) at the first keyframe, and reference there from elsewhere via property.
You are trying to return an instance without instantiating it. Use the following code for instantiating the class first.
private static var _instance:Main;
public static function get instance():Main
{
if(_instance == null)
_instance = new Main();
return _instance;
}

AS3 How to add a Class on the stage from a timer?

lots of help from you guys :). My next question is here :).
I have timer in class MyTimer.as and Thief1_mc.as movie clip.
How can I addChild(Thief1_mc) on the stage from MyTimer? Everything looks simple, the only problem is "stage" property. MyTimer class cannot send "stage" as an argument because is not on the stage itself. I tried adding MyTimer on the stage in Main class like addChild (MyTimer), the trace says MyTimer is on the stage but I still cannot pass the stage argument to the Thief1_mc. I need this argument to be sent because the class Thief1_mc has to add itself on the stage using the property "stage".
The code:
public class Thief1_mc extends MovieClip
{
//this variable type Stage will contain stage
private var stageHolder:Stage;
public function Thief1_mc()
{
//constructor
}
//function that creates this object with passed "stage" argument from the caller
public function createItself(st):void
{
//variable that contain the stage so I can use this argument anywhere in the class
stageHolder = st;
//i have to refer to the stage by passed "st" parameter to create this object
stageHolder.addChild(this);
//initial position
this.x = 380;
this.y = 230;
}
}
}
MyTimer class and "_thief1.createItself(stage)" caller with stage arument
public class MyTimer extends Sprite
{
private static var nCount:Number = 120;
private static var currentCount:Number;
private static var _timer:Timer = new Timer(1000,nCount);
private static var _timerDispather:Timer;
private static var _thief1:Thief1_mc = new Thief1_mc ;
public function MyTimer()
{
// constructor code
}
//another timer
private static function increaseInterval(interval:int):void
{
_timerDispather = new Timer(interval);
_timerDispather.addEventListener(TimerEvent.TIMER, onUpdateTimeAnotherTimer);
_timerDispather.start();
}
//another timer;
private static function onUpdateTimeAnotherTimer(e:Event):void
{
_thief1.createItself(stage);//the most important part
}
public static function activateTimer():void
{
currentCount = nCount;
_timer.addEventListener(TimerEvent.TIMER, onUpdateTime);
_timer.start();
}
public static function deactivateTimer():void
{
_timer.removeEventListener(TimerEvent.TIMER, onUpdateTime);
_timer.stop();
_timer.reset();
currentCount = nCount;
//another timer
_timerDispather.removeEventListener(TimerEvent.TIMER, onUpdateTimeAnotherTimer);
_timerDispather.stop();
_timerDispather.reset();
}
private static function onUpdateTime(e:Event):void
{
currentCount--;
if (currentCount == 0)
{
_timer.removeEventListener(TimerEvent.TIMER, onUpdateTime);
_timer.stop();
_timer.reset();
}
}
}
}
Your code is backwards in a few places. It does not flow very nicely, and the issues you having now are going to be tenfold at some stage in your project.
Firstly, your MyTimer class should not be extending Sprite. It does not get rendered and does not represent anything graphically.
Secondly, your timer class is taking on more than it should. I would revise it to manage your timers and timer events only. Create a list within your timer class that will contain some other elements which can have a method triggers to do other stuff, like creating and adding Thief1_mc.
A simplified version of this might look like:
public class Updater
{
private var _timer:Timer;
private var _toUpdate:Vector.<IUpdatable> = new Vector.<IUpdatable>();
public function Updater()
{
_timer = new Timer(60);
_timer.start();
_timer.addEventListener(TimerEvent.TIMER, _notifyUpdatables);
}
private function _notifyUpdatables(e:TimerEvent):void
{
for each(var i:IUpdatable in _toUpdate)
{
i.update(this);
}
}
public function addUpdatable(updatable:IUpdatable):void
{
_toUpdate.push(updatable);
}
public function removeUpdatable(updatable:IUpdatable):void
{
var index:int = _toUpdate.indexOf(updatable);
if(index >= 0) _toUpdate.splice(index, 1);
}
}
From here we need to create an interface which we will implement on classes that we want to be able to call update() on each time the Updater timer ticks:
public interface IUpdatable
{
function update(updater:Updater):void;
}
Now what I would do in your case is have a class that does extend Sprite and manages the graphics of the application / game. It will implement the IUpdatable interface like I have described and also could deal with adding your Thief1_mc:
public class View extends Sprite implements IUpdatable
{
public function update(updater:Updater):void
{
// Create a Thief.
var thief:Thief = new Thief();
updater.addUpdatable(thief);
addChild(thief);
}
}
Your Thief can take advantage of the IUpdatable interface we have and be added to the update queue when it is created, as I've done above. Just to have a complete example, here's the Thief class:
public class Thief extends Sprite implements IUpdatable
{
public function update(updater:Updater):void
{
// Make this Thief so some stuff.
//
}
}
And here's how you can tie it all together in your document class:
public class App extends Sprite
{
private var _updater:Updater;
private var _view:View;
public function App()
{
_updater = new Updater();
_view = new View();
_updater.addUpdatable(_view);
stage.addChild(_view);
}
}
This might be a bit overwhelming at first, and seem like a lot of work, but you now have a nice clean foundation to add more elements easily.
Rather than having your one class trying to manage timers and add Thieves like you had initially, we've separated the responsibilities and tightened up the flow a little. The Updater deals purely with storing IUpdatable instances and calling their update() method each time the Timer within it ticks. The View class manages the graphics and will also add a Thief each time it is updated via the Updater. The View was added to the stage initially, so all you need to do is add the thieves into itself to have them show up.
If you take this and restructure how the timers work within Updater, I think you'll be where you wanted but with a significantly better understanding and structure.

as3 declaring a GLOBAL variable - in TIMELINE / outside of CLASS

I am looking to declare a GLOBAL VAR in the main time line.
Then I need to access that GLOBAL VAR from another externally loaded SWF's.
QUESTIONS:
How do I create the global var in the main timeline?
How do I access that var in externally loaded swf files?
First, you shouldn't use any global/static state. In your situation this is even more true, because Singletons are a royal pain in the butt across different applicationDomains.
Instead, you should use something called Dependency Injection. Think of your little swfs as starving orphans. When they have loaded, they don't run up to your main swf and pick its pockets. Instead, the main swf magnanimously presses money into their little hands.
So, how do we make this happen? One way is that we could compile a reference to their Document class(es) into the main swf, and then we could set a variable that the Class exposes. However, this can get pretty heavy and isn't really necessary.
Instead, you can write something called an Interface, which defines the "idea" of an orphan.
It might look something like this:
public interface IOrphan {
function get alms():Number;
function set alms(value:Number):void;
}
Note that you have to use getters and setters with Interfaces, because you can't use them to define vanilla variables. However, that's going to work out great for our actual Orphan:
public class Oliver implements IOrphan {
private var _alms:Number;
private var _totalAlms:Number;
public var tf:TextField;//put this on stage and allow Flash to populate automatically
public function get alms():Number {
return _alms;
}
public function set alms (value:Number):void {
_alms = value;
_totalAlms += _alms;
updateAlmsMessage();
}
private function updateAlmsMessage():void {
tf.text = 'That was a donation of ' + _alms + '.\n'
'I now have ' _totalAlms + '.\n'
'Please, sir, can I have some more?';
}
}
Now, all you need to do is populate that variable on load. There are several ways you can do this, such as watching the stage for IOlivers to be loaded, or you could be more direct about it:
private function loadSwf(url:String):void {
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
var request:URLRequest = new URLRequest(url);
loader.load(request);
addChild(loader);
}
private function completeHandler(e:Event):void {
((e.target as LoaderInfo).content as IOrphan).alms = .25;
}
If these are variables that you only want to set once and will never change, you can just create a class that holds static constants.
package
{
public class Env
{
public static const WHATEVER:String = "Whatever!";
public function Env()
{}
}
}
Then you could access them later in your program like so:
trace(Env.WHATEVER);
However, if you want global variables that can change, I like to handle this by using a singleton class.
package
{
import flash.events.EventDispatcher;
public class Control extends EventDispatcher
{
//---------------------------------------
// PRIVATE & PROTECTED INSTANCE VARIABLES
//---------------------------------------
private static var _instance:Control;
//---------------------------------------
// PUBLIC VARIABLES
//---------------------------------------
public var whatever:String = "Whatever";
//---------------------------------------
// PUBLIC METHODS
//---------------------------------------
public static function get instance():Control
{
return initialize();
}
public static function initialize():Control
{
if (_instance == null)
{
_instance = new Control();
}
return _instance;
}
//---------------------------------------
// CONSTRUCTOR
//---------------------------------------
public function Control()
{
super();
if (_instance != null)
{
throw new Error("Error:Control already initialised.");
}
if (_instance == null)
{
_instance = this;
}
}
}
}
The difference here is that you need to grab the instance of your singleton before you can get to what's inside it. It'd look a little bit like this.
private var _control:Control = Control.instance;
// Reading a global variable
trace(_control.whatever);
// Change a global variable
_control.whatever = "Foobar!";
So whenever you change "whatever", that variable will change for all loaded SWFs. If you want to be really fancy about it, you could use getters/setters in your singleton rather than simple public variables.

Cleaning ArrayCollections

Initially I had ten arraycollections declared in a flex module, which I thought, were causing a memory leak. So I separated them in a single class, which I would clean using the "destroy" method I created inside it. Would this work?
I hate the question title excuse me. But I wouldn't write it like "Seducing the garbage collector"
[Bindable]
public class Cfd
{
private static var instance:Cfd = new Cfd();
private var _cfds:ArrayCollection = new ArrayCollection();
// Constructor
public function Cfd(){
if (instance) { throw new Error('Cannot create a new instance. Must use Cfd.getInstance().') }
}
public static function getInstance():Cfd{
return instance;
}
public function get cfds():ArrayCollection{
return _cfds;
}
public function set cfds(value:ArrayCollection):void{
_cfds = value;
}
public function destroy():void{
if(_cfds != null){
_cfds.removeAll();
}
}
}
Whenever you use Singletons like this, you pretty much guarantee a memory leak, because you're probably listening to the ArrayCollection (and maybe items within it) from all over the place. When you explicitly provide a reference to an object through a getter/setter pair, you can add the listener in the setter and removie it when the value is reset.
Check out http://www.developria.com/2010/08/rethinking-addeventlistener-an.html for more on what's happening.
For more on why you should avoid Singletons http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars
Just null the object should do it unless you have listeners attached to it.
cfds = null;
I have never seen [Bindable] used on the class itself, so not sure what you are trying to do there.
package{
public final class Cfd{
private static var instance;
private var _cfds:ArrayCollection = new ArrayCollection();
public function Cfd(singletonEnforcer:MySingletonEnforcer){
if (instance) { throw new Error('Cannot create a new instance. Must use Cfd.getInstance().') }
}
public static function getInstance():Cfd{
if (instance == null)
instance = new MySingleton(new MySingletonEnforcer());
return instance;
}
// don't see a real need for setter/getters here
public static function get cfds():ArrayCollection{
return _cfds;
}
public static function set cfds(value:ArrayCollection):void{
_cfds = value;
}
// I wouldn't even use a destroy on this class since it is a singleton.
// just set the data to null Cfd.cfds = null
public static function destroy():void{
_cfds = null
}
}
}
//this is in Cfd.as but is outside the package block
class MySingletonEnforcer {}