I am having some issues with AS3 Object Arrays. I am trying to make an inventory system, which the user can navigate left and right (which is working). When the user presses ENTER the item should then equip.
I was going to use switch and case to equip the items, as there will only be around 8 items to the game. I get the result [object purpleSword] when using trace, but my switch isn't getting any results or firing anything. I need the equipItem function to find the purpleSword that's ben found in the arrayItems. Items are added to the arrayItems when picked up off the floor.
Does anyone have any tips for using Objects for an RPG inventory system? Many thanks in advance.
public var arrayItems: Array = new Array();
if (keyEvent.keyCode == Keyboard.ENTER) {
if (currentScreen == "inventory") {
if(inventoryCurrent >= 0) {
var actualCurrentItem = inventoryCurrent - 1;
equipItem(arrayItems[actualCurrentItem]);
}
}
}
public function equipItem(itemNumber) {
switch(itemNumber) {
case "purpleSword":
trace("equip purple sword");
break;
}
}
AS3 has a type system, you should use it to help you understand your own errors, and help others understand your code (like us).
Given the fact that you say your output gives you [object purpleSword] I can assume you have a class purpleSword. My guess is that this is an exported symbol, not a .as class file, but it could be either.
But those are all guesses because you haven't provided any type information. For example:
arrayItems:Array could contain anything, and you haven't told us what. You could use a items:Vector.<Object> to store objects, or Vector.<Sprite> to store symbols exported from your Flash library, or better yet create an InventoryItem class and use a Vector.<InventoryItem> to store them.
var actualCurrentItem should be var actualCurrentItem:int
equipItem(itemNumber) should be equipItem(itemNumber:int):void?
If you do this, you will realize (either through your own observation or the compiler telling you) that your equipItem() function is wrong: it expects an itemNumber but it will receive an object.
If my prior assumptions were correct, you could do this:
public var items:Vector.<Object> = new <Object>[];
if (keyEvent.keyCode == Keyboard.ENTER) {
if (currentScreen == "inventory") {
if(inventoryCurrent >= 0) {
var actualCurrentItem:int = inventoryCurrent - 1;
equipItem(items[actualCurrentItem]);
}
}
}
public function equipItem(item:Object):void {
switch(item.constructor) {
case purpleSword:
trace("equip purple sword");
break;
}
}
This works because Object/constructor is a reference to the class of any object, ie purpleSword class, etc. However, you really should use something more concrete than Object, which could be any kind of object and tells you nothing about what kind of properties it might have.
Related
This is mostly a question about code design. What you see here is a very condensed version of the original code.
An example of the code is:
player.rest();
This makes the player sleep.
I have other methods such as walk, run, talk etc. which all work great. The one that is a problem is polymorph. It changes the player from a human object to another object. The solution I came up with is this:
class main
{
var human:Human = new Human;
var alien:Alien = new Alien;
var cow:Cow = new Cow;
var player = human;
enterframe loop{
//other code
if (player does something)
player.polymorph = "alien";
switch (player.polymorph)
{
case "alien":
player = alien;
break;
case "cow":
player = cow;
break;
//etc
}
player.update();
}
}
I want something that looks like this:
class main
{
var human:Human = new Human;
var alien:Alien = new Alien;
var player = human;
enterframe loop
{
player.polymorph(alien);
}
}
I know my original solution is the wrong way to go about things as it encourages spaghetti code. How could I do it differently? I don't mind a whole rewrite, but need an example to help push me in the right direction. I hope this makes sense and thanks for the help.
If the second one can work, what would the polymorph function look like?
I thought of making a class called player and changing what that extends, but to my knowledge that can't be done? Plus I would like to change the character to something already in game rather than a new object.
One solution to your problem would be using a single class, in this case, your Player class, and a finite state machine. You'd have a Player class, which can be set to different states, such as HUMAN, ALIEN, COW, etc. When a polymorph event occurs you update the Player's state, perhaps by calling an initState() method, and handle the logic for being a human, alien, cow, accordingly in whatever method updates your player.
Assuming the player has an update() method it could contain the following:
switch (state) {
case ALIEN:
// do alien stuff
case COW:
// do cow stuff
case HUMAN:
// do human stuff
}
Next, instead of handling the various polymorph states in a switch statement, your Player class could have a polyMorph method that takes a state as a parameter:
public function polymorph(newState:Int) {
state = newState;
initState(state); // You could even just call the initState method instead, and completely omit the polymorph method
}
Using a finite state machine here would eliminate the need for numerous objects.
Inside my classes, I invoke this function
MovieClip(root).increaseScore();
which handles the score in the main .as file.
It all works fine during the execution of the level. However when the level is finished and the screen goes to another frame, the game crashes and gives me this error
TypeError: Error #1009: Cannot access a property or method of a null
object reference.
on the line above.
How do I fix this?
Thanks
edit:
This is were I tell it to addScore, this is in the GameController.as file
private function removeBubble(bubble, addScore:Boolean)
{
var delay:Timer = new Timer(200, 1);
delay.addEventListener(TimerEvent.TIMER_COMPLETE, function(e:TimerEvent)
{
if(bubble.parent==mcGameStage)
{
var j:int = bubbleList.indexOf(bubble);
bubbleList.splice(j,1);
if(addScore) bubble.addScore();
mcGameUI.txtScorePlayer.text = String(playerScore);
mcGameStage.removeChild(bubble);
}
e.currentTarget.removeEventListener(e.type, arguments.callee);
checkWin();
});
delay.start();
}
here is the checkWin function:
private function checkWin()
{
if (playerBlue + playerRed + playerYellow + playerOrange + playerPurple + playerGreen == 0)
{
gameWin();
}
}
private function gameWin()
{
while (bubbleList.numChildren > 0)
{
bubbleList.removeChildAt(0);
}
mcGameUI.btnMixBlue.removeEventListener(MouseEvent.CLICK, mixBlue);
mcGameUI.btnMixRed.removeEventListener(MouseEvent.CLICK, mixRed);
mcGameUI.btnMixYellow.removeEventListener(MouseEvent.CLICK, mixYellow);
mcGameUI.btnNeedle.removeEventListener(MouseEvent.CLICK, activateNeedle);
mcGameStage.removeEventListener(Event.ENTER_FRAME,update);
mcGameStage.removeEventListener(MouseEvent.CLICK, checkToHit);
removeEventListener(Event.ADDED_TO_STAGE, gameAddedToStage );
stage.removeEventListener(KeyboardEvent.KEY_DOWN,keyDownHandler);
stage.removeEventListener(KeyboardEvent.KEY_UP,keyUpHandler);
if (mouseCursor != null)
{
mouseCursor.removeEventListener(Event.ENTER_FRAME,followMouse);
mouseCursor = null;
}
gotoAndPlay("level1win");
}
And inside my classes,
public function addScore()
{
root["increaseScore"]();
}
This is what increaseScore does
public function increaseScore()
{
playerScore += 1000;
}
So where is the null object? D:
Also I am very inexperienced using the debugger so I apologize if this could be easily solved with that. I tried it and couldn't figure it out before coming here.
What is OPP method?
What is FrameScript?
Also, the class is MovieClip
thanks :)
Fixing this is done with the help of the debugger!
The error is telling you that something is null and you're trying to access it. That's not good, so let's think what could be null?
1) Your movie clip instance might be null.
2) increaseScore() method of your movie clip instance might try to access something that is null
You didn't post any code that I could analyse at the time I'm writing this answer, so I can't say for sure.
Some possible problems:
Your class is not called MovieClip, but you're just trying to cast your root object to a MovieClip. Thing is, MovieClip's don't have a increaseScore() method. You should instead call the increaseScore() method with
root["increaseScore"]();
This will call the method of your root timeline, but since we are using weak-typing, you might have problems with debugging it later. But I guess that's the price you pay when writing all the code in a frame instead of using OPP approach.
The true reason here is said in the error: Cannot access a property or method of a null object reference.
What this means is that MovieClip(root) is null (it doesn't even bother to check if the function is present). The reasons for this might be three:
Your document class is not MovieClip (could be Sprite).
The class you use root in, is not DisplayObject. The property is part of the DislpayObject class and so if you use it on other kind of classes that does not extend it, it won't work.
You are using this piece of code before you have added the instance to the stage (most probable cause). The root property represents the top-most display object in the tree. If you haven't added the child to the tree, it has no roots :) Check out parent - the error should be the same.
Okay so I have a function called changeHandler - it is called by several eventListeners in other functions. I want to write several if statements that evaluate the source of function call and change the dataProvider of my ComboBox depending on the originating function. Example: one of the many functions is called displayCarbs() and has an eventListener like so:
function displayCarbs(event:MouseEvent):void {
myComboBox.addEventListener(Event.CHANGE, changeHandler);
}
(I've removed all of the unnecessary code from the function above)
The if statement inside the changeHandler will look something like this:
if (****referring function = displayCarbs****) {
myComboBox2.dataProvider = new DataProvider(carbItems);
}
I've searched high and low for something that can achieve this, but I just don't have a good enough grasp of AS3 or vocabulary to describe what describe what I mean to get the answer from Google.
The simplest way I can think of... Couldn't you simply create a text string that updates to the name of function before going to changeHandler then in turn changeHandler can check string content and act accordingly..
public var referring_function:String;
function displayCarbs(event:MouseEvent):void
{
referring_function = "displayCarbs";
myComboBox.addEventListener(Event.CHANGE, changeHandler);
}
function displayCarbs(event:Event):void
{
if (referring_function == "displayCarbs")
{ myComboBox2.dataProvider = new DataProvider(carbItems); }
if (referring_function == "displayOthers")
{ myComboBox2.dataProvider = new DataProvider(otherItems); }
// etc etc
}
I cant remember right now if you need == or just = when checking the If statement against strings.
I know there is an accepted answer already, but based on what I gleaned about the problem, here is a solution that wouldn't require adding another variable to check :
function displayCarbs(event:MouseEvent):void
{
myComboBox.addEventListener(Event.CHANGE, changeHandler);
}
function changeHandler(event:Event):void
{
var comboBox:ComboBox = event.target as ComboBox;
if (comboBox.dataProvider == uniqueProvider)
{
myComboBox2.dataProvider = new DataProvider(appropriateItems);
}
}
This should work if the second dataProvider is determined based on the first dataProvider. This of course requires that your uniqueProvider is a class member variable so it has scope within the handler.
I am currently working with some code that my co-worker wrote. Here is a simplified look at it:
The People class:
package model{
public class People extends Array{ // NOTE: it is not dynamic
public function toXML():XML {
var out:XML = <people/>;
for each(var per:Person in this){
out.appendChild(per.toXML());
}
return out;
}
}
}
Which is basicly an Array of Persons:
package model{
public class Person {
public var name:String;
public var phoneNumber:String;
public function Person(name:String, phoneNumber:String){
this.name = name;
this.phoneNumber = phoneNumber;
}
public function toXML():XML {
var xml:XML = <person/>;
xml.#name = name;
xml.#phone = phoneNumber;
return xml;
}
}
}
This is basicly how my co-worker is using the code:
var people:People = new People();
people.push(new Person("Jake", "902 825-4444"));
people.push(new Person("Bob", "514 444-3333"));
return people.toXML().toXMLString();
Note: The he adds Person objects but he never looks at what is in the People Array except to print out the XML
Fast-forward (do people still know that this means?) to the present. I now need to look inside the People class and do something like this:
var people:People = ... init and fill with Person objects
for(var i:int=0; i<people.length(); i++){
doSomething(people[i]); // <-- Error thrown here.
}
Unfortionatly this throws this error:
ReferenceError: Error #1069: Property 0 not found on model.People and there is no default value.
at runner::Runner$/newUse()
at ExtendsArray/start()
at ExtendsArray/___ExtendsArray_Application1_initialize()
at flash.events::EventDispatcher/dispatchEventFunction()
What should I do?
Edit, Aside: Extending Array is not my doing this is part of our old model. We are moving away from the old model because it is full of garbage like this. I just need to grab this stuff from the old model to convert it into the new model. I would like to tear this code out of our product but the cost of doing that is probably not warranted.
What should I do?
Use only class methods to access and set items in your "array", don't use Array-specific syntax. And/or make the class dynamic.
EDIT I think you can leave everything as is and avoid making your class dynamic by defining only one additional method for item access (if it's not there for some reason). Something like:
public functon getItem(index:uint):*
{
if (index >= length) {
return null;
}
return this.slice(index, index+1)[0];
// this creates a redundant array on the fly, sorry.
}
// usage:
var people:People = ... init and fill with Person objects
for(var i:int=0; i<people.length(); i++){
doSomething(people.getItem(i));
}
And I know that is not the way it's meant to be answered on stackoverwlow, but... I can't hold it. ))
Anything extends Array -- is a heresy. If I see that in production code, I'll immediatelly proceed to initiating a "purge the unclean" sequence.
Just try googling for the subject a little bit, and you will see, that almost no one has got alive and well from an activitiy like that.
The main rational reason why this abomination is not meant to exist (aside form it being a heresy) is that you can not use array access [], you can not instantiate it like a normal array with items and you can not set items through array syntax [] and be notified of the changes somewhere in your class. But it is an Array by the right of birth, so any of your fellow developers not familiar with the subject may try to use it like a normal Array, because it's quite natural. And then they'll probably post another question on stackoverflow.
So, if you are still limited to just using class methods, what's the point in extending anyway? Whay not use neat aggregation/composition or proxy ways?
It's amazing, but there's even an article on extending Array on adobe.com. What they do not mention there is that you are likely to start burning in hell right away.
Array.length is a getter: it returns an int. people.length() is the same as calling 3(). I don't know how it worked when you tested that.
It looks like you'd be better off implementing something like IList and have addItem push into a Vector.<Person>. That will guarantee that you only have Person objects.
You should probably should not be extending Array. If you want to extend anything, extend Proxy (You can even use the ProxyArray class example with a Vector.<Person>). Extending top level classes (with the exception of Object) is often an invitation for confusion.
If you really, really want to extend Array, you have to make the class dynamic -- you are arbitrarily assigning and removing properties.
This looks like it works:
var s:String = "";
for each(var per:Person in people){
s += per.name + " ";
}
The People class has to be scripted as public dynamic class since it is extending the Array class.
From this Adobe help article: A subclass of Array should use the dynamic attribute, just as the Array class does. Otherwise, your subclass will not function properly.
You used the "push" function which apparently created an associative array.
Associative arrays can not be called by index. They can also not be reversed or have their order changed.
You need to access them by using the for..in loop
for (var key:String in people) {
trace("person : " + (people[key] as person).name);
}
// or as you found out the for each.. in
for each(var person:Person in people){
trace("person : " + person.name);
}
The arr.length() function of an associative array will always be 0 and you saw that with your error.
//Try this example you will see how an array can act like an array as we know it and an object.
var a:Array = [];
a[0] = true;
a.push(true);
a.push(true);
a["foo"] = true;
a.push(true);
a.push(true);
a.bar = true;
trace("for loop\n");
for(var i:int = 0, ilen:int = a.length ; i < ilen ; i++){
trace(i,a[int(i)]);
}
trace("\nfor...in loop\n");
for(var key:String in a){
trace(key,a[key]);
}
How would you adhere to the "Tell, don't ask" principle (henceforth "the principle") in the following simple scenario? In a Tetris game, I have Board, BlockGrid and Piece classes relevant to the following example:
public class Board
{
private var fallingPiece:Piece;
private var blockGrid:BlockGrid;
...
public function moveFallingPiece(xDirection:int, yDirection:int):void
{
blockGrid.movePiece(fallingPiece, xDirection, yDirection);
}
}
Once fallingPiece is placed in the bottom row of BlockGrid, it should no longer be the "fallingPiece". Am I right in that I'm not violating the principle with the following?
if(blockGrid.getPiecePosition(piece).y == 0)
{
fallingPiece = null;
}
But is that really different from this, which I think clearly violates the principle?
public function moveFallingPiece(xDirection:int, yDirection:int):void
{
if(blockGrid.getPiecePosition(piece).y > 0)
{
blockGrid.movePiece(fallingPiece, xDirection, yDirection);
}
else
{
fallingPiece = null;
}
}
I'm not assuming that I've designed these class relationships in the proper way to work with the principle. Please advice on an alternate design if that's what I'm missing.
EDIT, Proposed solution:
I went with the answers proposing "command feedback" via events. Board tells BlockGrid to move a piece. BlockGrid's movePiece method dispatches MOVED_TO or MOVE_FAILED events depending on the result, which Board can listen to and use to determine whether a piece has stopped falling. Please don't hesitate to provide feedback on this solution.
public class Board
{
...
public function Board()
{
...
blockGrid.addEventListener(PieceMoveEvent.MOVE_FAILED, onPieceMoveFailed);
...
}
public function moveFallingPiece(xDirection:int, yDirection:int):void
{
blockGrid.movePiece(fallingPiece, xDirection, yDirection);
}
public function onPieceMoveFailed(event:MovePieceEvent):void
{
if(event.instance == currentlyFallingPiece && event.fromPosition.y != event.toPosition.y)
{
currentlyFallingPiece = null;
}
}
I think, to better follow the Tell, Don't Ask principle, you should have blockGrid notifying your Board class when fallingPiece has reaches it's resting point. In both scenarios above, you are asking blockGrid if the piece's position.y == 0 in order to determine whether or not fallingPiece should be null. Instead, you want blockGrid to tell the Board class that fallingPiece.y has hit 0.
What you are looking for is Event driven programming. You need a Listener interface with a method called .event() and an Event interface to represent the events. Objects will register with other objects ( callbacks ) to the Listener interface.
when you create a Piece and Board they should implement the Listener interface. Then you can set the Board with registerListener(board); Then when things happen inside Piece it will loop thru all the registered listeners and call .event(event) on each. Same with the Board, call board.registerListener(piece) each time you create a new piece, as it decides things are happening it can tell all the registered listeners what has happened. Then you can tell a piece it is no longer falling by the Board object deciding this. Here is the obligitory Wikipedia entry.
I would expect a class representing each shape (without position information), a controller containing a shape, position and orientation, and another class representing the current resulting grid of "landed" shapes. The landed-grid would have a
testLanded(shape, shapePosition, orientation)
method which would be called before/after each move operation to decide if the shape is to join the landed grid or should move and stay as the falling piece.
I'm going on the idea of not giving data to objects that shouldn't really own that data - but I've never implemented Tetris...
You may need to rethink your design. Does Board really need to track the falling piece or should that belong to BlockGrid? Iron out who owns what behavior.
Keep position information on your Piece class and possibly have your Piece class hold an instance of the BlockGrid.
You can then try something like this in your Board class...
public function moveFallingPiece(xDirection:int, yDirection:int):void
{
blockGrid.moveFallingPiece(xDirection, yDirection);
}
Then in BlockGrid's moveFallingPiece method...
public function moveFallingPiece(xDirection:int, yDirection:int):void
{
fallingPiece.move(xDirection, yDirection);
}
In Piece's move method, add your logic...
public function move(xDirection:int, yDirection:int):void
{
setPosition(xDirection, yDirection);
if (getPosition().y <= 0)
{
blockGrid.setFallingPiece(null);
// this can bubble up to Board if need be
}
}
Not sure of all the power of AS3, but it would make sense to use abstractions here. (i.e., have your Piece class depend on ITrackFallingPieces instead of BlockGrid and have BlockGrid implement ITrackFallingPieces).
Good luck!