I have a menu page with one button, which leads to another page by adding a child and removing one. Very simple. From here l have a page which allows many buildings to be clicked, which opens a new page (a mini game page). I have done this with setters and getters as shown below:
Main.as (gets value from the getter in MainPage)
public function onEnterFrame(event:Event):void
{
//Swaping pages with a getter and setter
if (mainPage.stage && mainPage.getNextLevel() == 1)
{
addChild(miniGameOne);
removeChild(mainPage);
}
if (miniGameOne.stage && miniGameOne.getNextLevel() == 2)
{
addChild(mainPage);
removeChild(miniGameOne);
}
}
MainPage.as (with all the buildings)
public function onAddedToStage(event:Event):void
{
doctor.addEventListener(MouseEvent.CLICK, onDoctorClick);
}
public function onDoctorClick(event:MouseEvent):void
{
setNextLevel(1);
}
public function setNextLevel(passLevel:int)
{
nextLevel = passLevel;
}
public function getNextLevel():int
{
return nextLevel;
}
MiniGameOne.as
Here it says when the mini game is complete then set the page to 2, which is adding the MainPage.as and removing MiniGameOne.as
public function onEnterFrame(event:Event):void
{
healthbar.meter.scaleY = life/100;
if (life < 1)
{
life = 1;
//Make sure it isn't visiable
healthbar.meter.alpha = 0;
//New Function
gameComplete();
}
}
public function gameComplete()
{
//Level Complete, Set new Level
setNextLevel(2);
}
I have a problem, when l enter a page (clicking on a building) then return to the original page and click on the same building l can't open the same page again, can anyone explain what is happening here? Thanks.
That happens because you have defined and are checking nextLevelfor both MainPage and MiniGameOne classes. When you change the level in miniGameOne instance to 2 it will be 2 until you change it to some other number.
From that point you have:
mainPage.getNextLevel() == 1
and
miniGameOne.getNextLevel() == 2
so in your enterFrame() both conditions are true and your miniGameOne is indeen added to stage but in the same frame it is removed and you mainPage is added again.
As a quickest fix you can change the line:
if (miniGameOne.stage && miniGameOne.getNextLevel() == 2)
to
else if (miniGameOne.stage && miniGameOne.getNextLevel() == 2)
But this would probably leave your mini game only until next frame is reached so you need to reset nextLevel value when you are back in mainPage :
if (miniGameOne.stage && miniGameOne.getNextLevel() == 2){
addChild(mainPage);
removeChild(miniGameOne);
miniGameOne.setNextLevel(0);
}
However what you have here is very poor practice.
Fist of all, change only that parts of your program on frame event which you actually need to update on every frame, or you really don't have any other way to determine when it should be updated
Secondly, don't duplicate variables that store you app states.
There are many ways you can apporach this problem but I will show one that is probably closest to what you have already:
1 Define level state in your Main class, store your levels in array/vector/whatever, pass instance of main to your levels.
/**Current level**/
private var cl:uint = 0;
/**All levels veiews that could be shown.**/
private var levels:Array = [mainPage, miniGameOne];
public function Main() {
for each (var l in levels) l.main = this;
}
public function get level():uint { return cl; }
public function set level(l:uint) {
this.removeChild(levels[cl]);
this.addChild(levels[cl = l]);
}
2 In your level classes define setter for main instance, you now can change current level stored in main.
private var m:Main;
public function set main(v:Main):void { m = v; }
public function onDoctorClick(event:MouseEvent):void{
m.level = 1;
}
public function gameComplete(){
//Level Complete, Set new Level
m.level = 0;
}
3 All other code, especially on frame event, can be removed.
The solution may not be ideal but I hope it will give you some idea.
Related
I am modifying code that I did not write and I have minimal experience with Actionscript.
Currently, menu items are greyed out when they are disabled. This is achieved through following classes:
DisabledComboBox (extends ComboBox)
DisabledList (extends List)
DisabledListItemRenderer (extends Label)
Menu constraints are stored in a large XML variable MenuChoiceXML. Each DisabledComboBox refers to an XMLlist which is a subset of MenuChoiceXML as its DataProvider. Each list entry within the XMLlists has an elementsID (integer).
Also within MenuChoiceXML is an XMLlist full menu constraints which are preassembled based on what is available in the database. They use the following form:
selecting 15 constrains 5, 12, 23
In other words, selecting 15 in one DisabledComboBox, greys out 5,12, and 23 in other DisabledComboBoxes, and they can't be selected.
Rather than greying them out and disabling the MouseEvent that selects them, I would like to completely remove them from the DataProvider XMLlist.
These are what I believe to be the relevant bits of code:
Item in constraint XMLlist
<Constraints>
<Cstr choice="DataProvider1" selectedValue="2" constrains="DataProvider2" denies="4,7,12" />
</Constraints>
TotalStateofProgram.as
public function applyConstraints():void
{
// before updating menus all constrained menus enable all menu choices, then turn on restricted options
for each(var menulist:String in constrainedMenuList)
{
for each(var xmlentry:XML in MenuChoicesXML.descendants(menulist).MenuItem)
{
xmlentry.#enabled=true;
}
}
// Save constraints in XMLlist
var cstrs:XMLList = MenuChoicesXML.descendants("Constraints").Cstr;
// go through each contraint in list of constraints
for each(var cstr:XML in cstrs)
{
var choice:String = cstr.#choice;
var value:String = cstr.#selectedValue;
// for each constraint - find if it is required constraint selectedValue matches current selection
var applies:Boolean = (design.getData(choice) == value);
if(applies)
{
var menuname:String = cstr.#constrains;
var denies:String = cstr.#denies;
var deniesarray:Array = denies.split(',');
// Go throught items which are denied by selection
for(var i:int = 0; i<deniesarray.length;i++)
{
var d:String = deniesarray[i];
// If something that is to be constrained is currently selected, unselect it from DisabledComboBox
if(design.getData(menuname) == d)
design.setData(menuname, "0"); // default elementsId for unselected is 0
// set disable menu choice for this elementsId
(MenuChoicesXML.descendants(menuname).MenuItem.(#elementsId == d)).#enabled = "false";
}
}
}
}
The preceding code resets all constraints, then uses the currently selected menu item to apply constraints to other menu items. The important bit is this line:
(MenuChoicesXML.descendants(menuname).MenuItem.(#elementsId == d)).#enabled = "false";
It appears to be looking up an item in an XMLlist and disabling it. Once disabled, this is seen by the DisabledList and DisabledListItemRenderer where the selected item is turned grey and the MouseEvent attached to it is disabled. (I will post the code which achieves this later, as I'm not 100% it's relevant)
I realize that ComboBox.removeItem() is normally used, but I am unsure how I can look up a ComboBox or DataProvider using the elementsID (as was done above).
Here are some questions I have:
Can I look up the ComboBox that has the XMLlist entry in its DataProvider and use ComboBox.removeItem()? How?
Can I use the fact that an XMLlist entry is disabled to remove it from the list?
Is there another way I'm not considering?
I will work on summarizing the code that greys out the list entry of the DataProvider. I am not sure if it's relevant because the greying out is performed on the text within the list which makes up the DataProvider for the DisabledComboBox, however I want to remove the element from the DisabledCombobox (which seems like less work).
Possibly Relevant Code:
DisabledComboBox.as
package com.cwmlib.controls
{
import mx.controls.ComboBox;
import mx.core.ClassFactory;
import mx.events.FlexEvent;
public class DisabledComboBox extends ComboBox
{
public function DisabledComboBox()
{
super();
this.dropdownFactory = new ClassFactory(DisabledList);
this.itemRenderer = new ClassFactory(DisabledListItemRenderer);
}
}
}
DisabledList.as
package com.cwmlib.controls
{
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import mx.controls.ComboBox;
import mx.core.ClassFactory;
import mx.controls.List;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListItemRenderer;
public class DisabledList extends List
{
public function DisabledList()
{
super();
this.itemRenderer = new ClassFactory(DisabledListItemRenderer);
}
private function itemDisable(event:MouseEvent):Boolean
{
var item:IListItemRenderer = mouseEventToItemRenderer(event);
if (item != null && item.data != null
&& ((item.data is XML && item.data.#enabled == 'false')
|| item.data.enabled==false || item.data.enabled=='false')
)
{
return true;
}
else
{
return false;
}
}
}
}
DisabledListItemRenderer.as
package com.cwmlib.controls
{
import mx.controls.Label;
public class DisabledListItemRenderer extends Label
{
private var _enabled:Boolean = true;
public function DisabledListItemRenderer()
{
super();
}
override public function set data(value:Object):void
{
if (value != null && ((value is XML && value.#enabled == 'false')
|| value.enabled == false || value.enabled == 'false')
){
this._enabled = false;
}
else
{
this._enabled = true;
}
super.data = value;
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (!this._enabled) {
textField.setColor(getStyle("disabledColor"));
}
else
{
textField.setColor(getStyle("color"));
}
}
}
}
So you want to remove some items in one combobox if you select something in another one? And probably display them again if you select something else? In this case a filterFunction sounds to be the right tool for that. Not tested but this will give you an idea:
// make a dedicated _myDataProvider for the filtered combobox and assign a filterfunction to it
_myDataProvider.filterFunction = filterCollection;
// Assign the dataProvider
filteredCombobox.dataProvider = _myDataProvider;
// filter the dataprovider when selection in the first combobox will change
selectionComboBox.addEventListener(Event.CHANGE, onComboBoxChange);
private function onComboBoxChange(e:Event):void
{
// refresh the dataprovider, that will apply the filter
_myDataProvider.refresh();
}
private function filterCollection(item:Object):Boolean
{
// here you need to know the condition for the filter depending on the selection in the first combobox (prbably the selected item in the selection combobox?)
// this filterCollection function will run trough each item in the _myDataProvider and return true (keep the item in the list) or false (the item will disappear)
if (condition)
return true;
else
return false;
}
Keep in mind that when the filter is applied the _myDataProvider will contain only currently visible items. If you'd need the full collection again you could reset the dataprovider with
_myDataprovider.filterFunction = null;
_myDataprvider.refresh();
EDIT:
This whole XML manipulation sounds like a pain to me. Get rid of the custom combo boxes and XML. Parse your XML and make two dataproviders - one for the selection combobox that will have value objects with this denies property:
public class SelectionVO
{
public var label:String;
public var denies:Array; // for example, ["2", "3", "4"];
}
And another one for the filtered combobox with a value object that will have the id to check if this item should be filtered out or not:
public class FilteredItemVO
{
public var label:String;
public var myID:String; // for example, "3"
}
And then when the selection combobox changes you filter the collection:
private function filterCollection(item:Object):Boolean
{
var selectedVO:SelectionVO = selectionComboBox.selectedItem as SelectionVO;
// show this item if its myID is NOT included in the selected vo's denies array
if (selectedVO:SelectionVO.denies.indexOf(item.myID) == -1)
return true;
else
return false;
}
I'm not really expert on OOP and as3 now. I'm making inventory system that will handle this things:
*when player collide with an "item", it will get it and send to the inventory array, etc...
here is the Player class constructor
as you can see i write item's instance name manually: "silver_key"
public function Player(player:MovieClip,loot:MovieClip,place:MovieClip)
{
// constructor code
_player = player;
silver_key = loot;//the item
_inventory = new InventorySystem(place);//InventorySystem Class
_player.addEventListener(Event.ENTER_FRAME,on_enter_frame);
addEventListener(EventDemo.EVENT_DEFAULT,onEvent);//custom event listener
}
This is the functions relative to it:
private function on_enter_frame(e:Event):void
if (_player.hitTestObject(silver_key))//check collision between player and item
{
dispatchEvent(new EventDemo(EventDemo.EVENT_DEFAULT));
}
private function onEvent(e:EventDemo)
{
_inventory.getitem(_loot);//function use in InventorySystem for array.push
removeEventListener(EventDemo.EVENT_DEFAULT,onEvent);//removes the listener when the item was sent to array
}
now my question is how can i be able to create a lot of items without manually writing their instance name. Thanks.
ps: if you still can't understand please comment.
To put it simply:
object["string_name"] = value
For a more thorough explanation, read on...
You need explicit names for each item, but you want to dynamically define those items without knowledge of what it could be. You can store this in a number of ways, but a basic Array or Object should work fine.
It looks like you were creating a movieclip to represent the player. As a class, the instantiation of the player class could itself be that movieclip, but be aware that MovieClips carry a lot of timeline baggage that can slow you down. For a more succinct/lightweight player class, extend Sprite and write your own properties/methods.
For example, let's imagine this is what a Silver Key object looks like:
item:Object = {
"name":"Silver Key",
"count":1,
"weight":3,
"icon":"silver_key.jpg"
}
In one object, we can define a name property to be whatever we want that type of item to be, as well as other pertinent properties that describe that item. Now, all we need to do is keep a list of saved items in the player class itself.
Player Class
package {
public dynamic class Player extends Sprite {
import flash.events.*;
import flash.display.*;
public var inventory:Object = {};
public function Player() {
addEventListener(Event.ENTER_FRAME, tick);
}
public function tick(e:Event):void {
// ENTER_FRAME stuff
}
public function loot(item:Object) {
if (inventory.hasOwnProperty(item.name)) {
inventory.item.count++
trace("You now have " + inventory.item.count + " " + item.name + "'s!");
} else {
inventory[item.name] = item;
trace("You have picked up a " + item.name)
}
}
public function drop(item:Object) {
if (inventory.hasOwnProperty(item.name)) {
inventory.item.count--
if (inventory.item.count == 0) {
trace("You no longer have " + item.name);
delete(inventory[item.name]);
} else {
trace("You now have one less " + item.name + ".");
}
}
}
}
}
Example Implementation
var player:Player = new Player();
player.loot(item);
// traces "You picked up a Silver Key"
player.drop(item);
// traces "You no longer have Silver Key"
This is a simple way of doing it, and truthfully, you may want to expand on it with your own item & inventory classes, as well as data sanitization/checking to prevent a corrupted inventory.
Update: "More than likely, you'll create a prototype of each item."
Here's what one could look like.
package {
public class ItemDefinition extends Object {
public var name:String = "Unnamed Item";
public var count:int = 1;
public var weight:int = 0;
public var icon:String = "unnamed.jpg";
public function ItemDefinition(Properties:Object) {
// By passing in an object, we can define only the properties we want to change.
for (var Name:String in Properties) {
// Only property names that match will overwrite the defaults.
if (this.hasOwnProperty(Name)) {
this[Name] = Properties[Name]
}
}
}
}
}
What's cool about this, is that we can define any number of properties and in any order without fear of adding a bogus value that could mess us up later. Furthermore, if we want to add a new property to what defines a Item, just add it as a public var at the top.
Next, we might define all of our items once at the top of our document, using a new ItemDefinition object.
var itemDefinitions:Object = {
'Silver Key':new Item({name:"Silver Key", icon:"silver_key.jpg", weight:3}),
'Gold Key':new Item({weight:10, name:"Gold Key", icon:"gold_key.jpg"}),
'Iron Key':new Item({icon:"iron_key.jpg", weight:2, name:"Iron Key"})
}
function populateItems(itemName:String, count:int = 20) {
if (itemDefinitions.hasOwnProperty(itemName)) {
for (var i:int = 0; i < count; i++) {
var item:MovieClip = new MovieClip();
item.x = randomNumber(0, this.loaderInfo.width);
item.y = randomNumber(0, this.loaderInfo.height);
item["itemDefinition"] = itemName;
}
} else {
trace(itemName + " is an invalid item.");
}
}
function randomNumber(low:Number=0, high:Number=1):Number {
/* Returns a random number between the low and high values given. */
return Math.floor(Math.random() * (1+high-low)) + low;
}
As you can see, I've added a couple helper functions to spit out those extra items on screen (randomly). Obviously, we'd need to have an addChild, and image loading, but that's really something you should implement.
Finally, we'll revise our loot call to pass the itemDefinition...
player.loot(itemDefinitions[item.itemDefinition]);
removeChild(item);
Worlds of possibilities. You'll likely make something far more robust which addresses the needs of your game, so this is only one way you might do it.
So... I'm working on a chess-game, and trying to make it so that a "public static boolean" (turn) dictates which player can make a move. This boolean is in a class (Board.as) which imports all the classes for all the chess-pieces (e.g. QueenW.as (for the White Queen)).
I've tried multiple ways: Trying to make functions not run anymore, and replacing the pieces (which are buttons) to other objects (non-clickable movieclips). Decided to go with the latter. I've traced the boolean in a chess-piece class, as well as the Board-class, in an ENTER_FRAME function. Both seem to trace it correctly when the value changes.
Problem is: Flash doesn't remove the chess-pieces and replaces them with a non-clickable object, even though the class in which it should happen (Board.as) does listen to the boolean when tracing. Anybody knows a solution?
A little piece of my code, which is relative to the problem:
Board class (which is the Documentclass for my .fla file)
package
{
import QueenWclass; //imports the class used for example.
public class Board extends MovieClip
{
public static var turn:Boolean = new Boolean; //creates public static bool.
var queenW:QueenWclass = new QueenWclass(); //creates aforementioned chess-piece.
var queenWnoturn:QueenWnoturn = new QueenWnoturn; //creates a non-clickable object.
}
public function Board()
{
turn = true;
this.addEventListener(Event.ENTER_FRAME, frameEnter);
addChild(queenW); //adds pieces to the screen.
}
if (turn == true)
{
}
if (turn == false)
{
removeChild(queenW); //Removes chess-piece.
addChild(queenWnoturn); //Adds a non-clickable object.
}
}
And my QueenWclass.as class:
package
{
public class QueenWclass extends MovieClip
{
var queenW:QueenW = new QueenW();
}
public function QueenWclass()
{
addChild(queenW);
this.addEventListener(MouseEvent.CLICK, CLICKqueenW);
}
function CLICKqueenW(event.MouseEvent):void
{
Board.turn = false;
}
}
I hope I wrote this example correctly and understandably. There's no real timelimit to my project as I already had to turn it in an hour ago (but still got a 6/10 because of effort and how far I've come with this rather complex game). I just want to finish it for myself... Thanks in advance!
Maybe the code has not been copied correctly or there is a small problem.
This code:
if (turn == true)
{
}
if (turn == false)
{
removeChild(queenW); //Removes chess-piece.
addChild(queenWnoturn); //Adds a non-clickable object.
}
Will only run once, when "Board" is created, it will not run when the state of "turn" changes.
Well, you have nothing that's listening for the boolean's change. The code that's checking the boolean is located in constructor, while the actual change is done in a MouseEvent.CLICK event listener. You have to either implement a function that's called repeatedly via Event.ENTER_FRAME listening, SetInterval(), or TimerEvent.TIMER (with a timer), or implement a publicly available property as a function, that would check which turn is it and do corresponding actions. The latter is a little better, as it works only when something is changed.
private static var _turn:Boolean=false;
public static function get turn():Boolean { return _turn; } // getter part
public static function set turn(value:Boolean):void // setter part
{
if (_turn==value) return; // no need to change turn
_turn=value;
if (_turn) YouGetATurn(); else EnemyGetsATurn();
// this part is what will get called when you change Board.turn
}
I'm rather new with AS3, so forgive me if my question appears stupid.
Let me clarify what I want specifically: The Player goes on a mission, in which he would earn points when he catches some objects on his way, for example object A gives one point, object B gives two points, object C gives bonus points, etc... IF the Player hits some obstacle on his way, he fails the mission and he has to replay the game until he reaches the destination (end of level).
So what I want is that how I can store the points that the Player earned before he hits the obstacle and the earned points would be accumulated every time the Player has to replay the game until he reaches the end of the game level so that the total points the Player earned would be the sum of all the points, including those that he earned before he actually reaches the end of the game level.
My Class Player is the subclass of the class Box that belongs to the framework WCK. Is there a way for me to implement Dependency Injection properly to pass the variables to the other class in order to store them there?
Thank you in advance for your help!
ADDED: Here is what I implemented based on Amy's example with some adjustment to suit the setup of my game. It still doesn't work as the value of variable _totalEarnedPoints did not update the variable _earnedPoints in Class PlayerClient via EventDispatcher when I tried to retrieve it back in Class Player for accumulating all the points. I'm not sure what I did wrong or missed anything else? Can anyone please point out and tell me how to retrieve back the variable _totalEarnedPoints from Class PlayerClient?
CLASS Player:
package
{
public class Player extends Box
{
private var contacts:ContactList;
private var myLoader:SWFLoader;
private var mcTemp:MovieClip;
private var bonusPoints:int = 0;
private var onePoint:int = 0;
private var twoPoints:int = 0;
private var objectACount:int = 0;
private var objectBCount:int = 0;
private var objectCCount:int = 0;
private var replayCount:int = 0;
protected var _earnedPoints:int;
private var myPlayerClient:PlayerClient = new PlayerClient();
}
public function Player()
{
super();
}
public override function create():void
{
super.create();
listenWhileVisible(this, ContactEvent.BEGIN_CONTACT, handleContact);
contacts = new ContactList();
contacts.listenTo(this);
}
public function handleContact(e:ContactEvent):void
{
//Detect collison with other static objects
var myObjectA:objectA = e.other.m_userData as objectA;
var myObjectB:objectB = e.other.m_userData as objectB;
var myObjectC:objectC = e.other.m_userData as objectC;
var myObstacle:obstacle = e.other.m_userData as obstacle;
if(myObjectC)
{
objectCCount++;
myObjectC.remove();
if (objectCCount > 0)
{
bonusPoints = bonusPoints + 5;
}
}
else if(myObjectA)
{
objectACount++;
myObjectA.remove();
if (objectACount > 0)
{
onePoint = onePoint + 1;
}
}
else if(myObjectB)
{
objectBCount++;
myObjectB.remove();
if (objectBCount > 0)
{
twoPoints = twoPoints + 2;
}
}
else if(myObstacle)
{
var myEarnedPoints:int = myPlayerClient.totalEarnedPoints;
_earnedPoints = bonusPoints + onePoint + twoPoints + myEarnedPoints;
dispatchEvent(new Event("EarnedPointChanged"));
myLoader = new SWFLoader("myMovie.swf",{container:swfHolder,alpha:0,onComplete:completeHandler});
//adds the loader content to the display list before raw content is loaded.
addChild(myLoader.content);
stop();
myLoader.load();
function completeHandler(e:LoaderEvent):void
{
replayCount++;
if (replayCount <= 1)
{
TweenMax.to(e.target.content, 1, {alpha:1});
mcTemp = myLoader.rawContent;
mcTemp.gotoAndPlay(1);
}
else if (replayCount >= 1)
{
mcTemp = myLoader.rawContent.stop();
myLoader.unload();
mcTemp = null;
}
}
myObstacle.remove();
}
}
public function get earnedPoints():int
{
return _earnedPoints;
}
}
CLASS PlayerClient
package
{
public dynamic class PlayerClient extends MovieClip
{
private var _totalEarnedPoints:int;
protected var _player:Player;
public function get player():Player
{
return _player;
}
public function set player(value:Player):void
{
if (value != _player)
{
if (_player)
{
//[prevent memory leaks][2]
_player.removeEventListener("EarnedPointChanged", updatePlayerScore);
//don't need this if we don't have a player
removeEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
_player = value;
if (_player)
{
//listen for changes
_player.addEventListener("EarnedPointChanged", updatePlayerScore);
//we added a listener to the new player, need to make sure is removed
addEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
}
}
protected function cleanUp(e:Event):void
{
_player.removeEventListener("EarnedPointChanged", updatePlayerScore);
}
protected function updatePlayerScore(e:Event):void
{
_totalEarnedPoints = _player.earnedPoints;
}
public function get totalEarnedPoints():int
{
return _totalEarnedPoints;
}
}
}
First, what you're doing is going to cause you a world of hurt unless your project is a banner or something that you know 100% that you will work on for 2 days and never look at again. See http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
What you need to realize is that your Points.pointsEarned looks like it will only ever change if you create a new instance of Player, and if you have several players, each new Player will have a higher number of points to start out with than the one before. That's probably not what you want. You don't really say what it is that you want, so let me throw out one thing that you could want and tell you how to code it. If that's not right, we'll go from there.
Edit:
OK, so now we know that you want to be able to just increment the earned points, so this Class now becomes much simpler (check the revision history of the post to see what parts were removed):
package{
public class Player extends EventDispatcher {
protected var _earnedPoints:int;
public function Player() {
super();//not passing a parameter, so target of events will be this
}
public function get earnedPoints():int {
return _earnedPoints;
}
public function set earnedPoints(value:int):void {
if (value != _earnedPoints) {
_earnedPoints = value;
//other Classes can listen for this to see if earnedPoints has changed:
dispatchEvent(new Event('earnedPointsChanged');
}
}
}
}
You didn't ask about this part, but I think it's a necessary part for you to understand. I'm going to explain it just in case it's not obvious. Your "objects" need to have a Class of their own:
package {
class TargetObject extends MovieClip {
public var points:int;
}
}
You can set those values when each TargetObject is created, or you can create several subclasses, where the value is hard-coded.
So now, whenever there's a collision, you can do something like this:
_player.earnedPoints += target.points;
When you set that variable, your PlayerClient will update, even when it is set from another Class.
In summary, you need to generate an event that other instances can listen for to update themselves:
package {
public class PlayerClient extends MovieClip {
public var playerScoreText:TextField;//assume this is on stage
protected var _player:Player;
//assume [dependency injection][1]
public function get player():Player {
return _player;
}
public function set player(value:Player):void {
if (value != _player) {
if (_player) {
//[prevent memory leaks][2]
_player.removeEventListener('earnedPointsChanged', updatePlayerScore);
//don't need this if we don't have a player
removeEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
_player = value;
if (_player) {
//listen for changes
_player.addEventListener('earnedPointsChanged', updatePlayerScore);
//we added a listener to the new player, need to make sure is removed
addEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
}
}
protected function cleanUp(e:Event):void {
_player.removeEventListener('earnedPointsChanged', updatePlayerScore);
}
/* This will trigger no matter what changes the value of
earnedPoints or bonusPoints.
*/
protected function updatePlayerScore(e:Event):void {
if (playerScoreText) /*in case it wasn't put on stage */ {
playerScoreText.text = _player.earnedPoints
}
}
}
}
I got my question resolved by myself: I used SharedObject method to store the points and then retrieve the point value back into my class Player for accumulation. EventDispatcher did pass the points to the other class, but all the points value get back to initial state once Player hits the obstacle and the game replays, which makes it difficult to retrieve the points I passed to the other class back to the class Player for accumulation. SharedObject method did help me resolve this problem for my case study.
I've subclassed the LabelItemRenderer class to create an expandable renderer for a spark list in my mobile app.
When the user selects an item, the renderer's size increases, additional data is shown. The renderer basically looks like this (I've removed the parts that don't matter here, so this is basically pseudo code).
public class PositionsGridRenderer extends LabelItemRenderer
{
public function PositionsGridRenderer() {
super();
addEventListener(MouseEvent.CLICK, expandHandler);
}
protected override function createChildren():void {
super.createChildren();
_dg = new DataGroup();
_dg.visible = false;
addChild(_dg);
}
private function expandHandler(event:Event):void {
if(_gridVisible) {
if(!_detailClicked) {
_dg.visible = false;
_gridVisible = false;
}
_detailClicked = false;
} else {
_dg.visible = true;
_gridVisible = true;
}
}
public override function set data(value:Object):void {
if(!value) return;
super.data = value;
var pos:Position = data as Position;
label = pos.positionName;
_dg.dataProvider = pos.positionSymbols;
}
protected override function measure():void {
!_gridVisible ? measuredHeight = 30 : measuredHeight = 30 + getElementPreferredHeight(_dg);
this.height = measuredHeight;
}
protected override function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void {
setElementSize(labelDisplay, unscaledWidth, 30);
setElementPosition(labelDisplay, 10,10);
if(_gridVisible) {
setElementSize(_dg, unscaledWidth, getElementPreferredHeight(_dg));
setElementPosition(_dg, 0, 30);
} else {
setElementSize(_dg, unscaledWidth, 0);
}
invalidateSize();
}
}
}
Works as expected, I'm just wondering if there's a way to reduce the amount of validation calls this renderer does when I expand it.
If it is clicked to be expanded the layoutContents and measure functions are both called three times in the following order: layoutcontents -> measure, layoutcontens -> measure, layoutcontents -> measure.
I'd understand them being called once because I invalidate the size, but three times seems odd.
Does anyone know why this is happening, or maybe even how to prevent this from happening?
The real question was why is the component going through three full renderer cycles? After some disussion, this is what we came across:
The first time the invalidate cycle is triggered is when a mouse down, or possibly a touch begin event occurs. This puts the component into the hover state; which causes a visual change in the component.
The second time the invalidate cycle is triggered is when the item is selected. This puts the renderer in the down state; causing a different visual indicator to be drawn.
The third invalidate cycle is caused by the component's own code; when layoutContents() calls invalidatesize()