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.
Related
I have been working on an AS3 project for some while and I think that I've hit a wall. My project requires a series of elements to be arranged in a Circular List, so I copied myself in a Circular List I had done before in C#.
Problem is, that one was heavily dependant on the usage of Generics. Now I don't have those.
Here are the codes. The T variable type represents the generics that I wish existed.
Node.as:
package
{
public class Node
{
var nodeContent:T;
var nextNode:Node;
function Node(nodeElement:T)
{
this.nodeContent = nodeElement;
}
}
}
CircularList.as:
package
{
public class CircularList
{
var head:Node;
var tail:Node;
var listLength:int;
function CircularList()
{
this.head = null;
this.tail = null;
this.listLength = 0;
}
function Add(addition:T)
{
adding:Node = new Node(addition);
if(this.head == null)
{
this.head = adding;
this.tail = adding;
head.nextNode = tail;
tail.nextNode = head;
}
else
{
tail.nextNode = adding;
tail = adding;
tail.nextNode = head;
}
listLength++;
}
function Find(requested:T):Node
{
var finder:Node = null;
var searching = head;
var i:int;
while(i <= listLength)
{
if(searching.nodeContent == requested)
{
finder = searching;
}
searching = searchig.nextNode;
i++;
}
return finder;
}
}
}
Is there a way to make this thing work without the generics?
EDIT: The real problem with this is that I want the NodeContent in the Node class to be an object. I want to make a list of people sitting on a circular table, basically, but I would like to have a code that I can reuse, rather than something specifically made for this problem
From the comments it seems like your best option here would be to use an interface.
Instead of using a type have all classes T implement an interface like INode. In this interface you can define all the functionality that your type T requires and implement it as needed in each of your implementing classes. This way you can change your function signatures to take type INode instead of Class or * and have a common set of methods that these functions can act upon.
function Add(addition:INode){
//add logic on INode
}
function Find(requested:INode):Node{
//find logic on INode
}
edit: a bit of info about interfaces,
http://active.tutsplus.com/tutorials/actionscript/as3-101-oop-introduction-to-interfaces/
say we have two Classes, A, B and each of these classes have a similar method, doTrace, that needs to be implemented differently. We can define an interface, implement it in both of these classes and pass that type into any method looking to call doTrace
Start with the interface called ITraceable,
public interface ITraceable{
function doTrace():void //all methods defined in interfaces are seen as public
}
Now our two Classes, A and B
public class A implements ITraceable { //implementing our interface, when we do this we need to define all methods in ITraceable
public function doTrace():void{
trace("I am A");
}
}
Do a similar thing for B
public class B implements ITraceable {
public function doTrace():void{
trace("I am B");
}
}
Now in some outside class we want to use this
public function letsTrace():void{
doTheTrace(new A()) //I am A
doTheTrace(new B()) //I am B
}
public function doTheTrace(object:ITraceable):void { //now we can pass both A and B into this function
object.doTrace(); //since ITraceable requires all objects that implement it have this method we can guarantee it will be here
}
Hope this helps you through your application
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 am creating a game using AS3. I need to display the final score for the player on the next key frame when the game is over. When the player collides with an enemy the players score increments by 1.
This is the code I have which updates the score.
var playerScore:int = 0;
function updateTextFields():void
{
playerScoreText.text = ("Score: " + playerScore);
}
function caught(enemy:MovieClip):void
{
enemy.removeEventListener(Event.ENTER_FRAME,dropEnemy);
removeChild(enemy);
playerScore++;
updateTextFields();
}
I need to show the final score for the player on the game over screen but because the value for playerScore is dynamic and increments by one I can't just display
playerScoreText.text = ("Score: " + playerScore);
as the variable default is 0.
I have tried but I can't figure out how to make it work.
var playerScore = playerScore;
If you want to use var in every frame you have to put it into a class (before the Main function). Also you should use public or private prefix. If you add private you ca use this variable only in this class, and public will enable you to use it in every class that is related to that file.Also it's good idea to use uint instead of int.
For example it should look like that:
package
{
//import...
public class Main extends //(you should write Sprite, MovieClip... there)
{
public var playerScore:uint = 0;
public function Main()
{
//constructor
}
}
}
If you get rid of the = 0 then it should work. playerScore is being set to 0 by the frame script. If all you have is the declaration var playerScore:int;, the variable will default to zero and Flash won't add the statement playerScore = 0 to the frame script (a function that's called every time the particular frame is reached).
In general, it's better to declare a class explicitly. The way Flash generates a class from scripts added to frames can be rather confusing, as a single statement can end up in two places. For instance, your above code will actually become the following:
package filename_fla
{
public dynamic class MainTimeLine extends MovieClip
{
public var playerScore:int;
internal function frame1:*
{
playerScore = 0;
}
public function updateTextFields():void
{
playerScoreText.text = ("Score: " + playerScore);
}
public function caught(enemy:MovieClip):void
{
enemy.removeEventListener(Event.ENTER_FRAME,dropEnemy);
removeChild(enemy);
playerScore++;
updateTextFields();
}
}
}
So I've never worked with Vectors in Flash before and I wanted to write a little test application with a Vector using a custom object. But I'm getting: error 1000: Ambiguous reference to Vector when I try launching the application. I can't figure out for the life of me what's wrong. I tried not using a custom object and just instantiating a string Vector from an online tutorial and I'm getting the same thing.
Here's what I got:
package
{
import TestPlayer; // The custom player class
import flash.display.MovieClip;
public class Vector extends MovieClip
{
private var array:Array = new Array();
private var vector:Vector.<TestPlayer>;
public function Vector()
{
array[0] = [0, 0, "Bob", false];
array[1] = [1, 0, "Frank", true];
array[2] = [2, 1, "Sarah", true];
Load();
}
private function Load():void
{
var aPlayer:Player = null;
vector = new Vector.<TestPlayer>();
try
{
var numRows:int = array.length;
for (var i = 0; i < numRows; i++)
{
aPlayer = new Player();
aPlayer.playerID = array[i][0];
aPlayer.playerName = array[i][1];
aPlayer.playerTypeID = array[i][2];
aPlayer.hasProgress = array[i][3];
vector.push(aPlayer);
}
}
catch (error:Error) { }
}
}
The custom player class looks like this:
package
{
public class TestPlayer
{
private var _playerID:int;
private var _playerName:String = "";
public function get playerID():int
{
return _playerID;
}
public function set playerID(value:int):void
{
_playerID = value;
}
public function get playerName():String
{
return _playerName;
}
public function set playerName(value:String):void
{
_playerName = value;
}
[...]
}
}
I don't know if it matters, but I'm working in Flash CS5, and I have a blank FLA that imports the class. No other errors so far. Hope you can help. Let me know if you need anymore info, thanks.
The ambiguous reference is because you've got a naming collision. The class you've written is named "Vector", which it can't distinguish from the top-level class Vector. The fix is simple, avoid naming your classes the same as a pre-existing class.
If both classes belong to separate namespaces, you can reuse class names, as long as you use thier fully-qualified name whenever you call the class.
Assume you have a class:
package foo.bar
{
class MovieClip
...
}
You could instantiate both types of MovieClips as follows:
flashMovieClip = new flash.display.MovieClip();
myMovieClip = new foo.bar.MovieClip();
Unfortunately, both your Vector and the flash Vector exist in the top-level namespace, so (AFAIK) there's no way of removing the ambiguity without renaming your class. For simplicity sake, avoid naming collisions and you should be golden.
I'm simply playing around with basic ActionScript 3 using Flash CS3 Pro.
I put in a keyframe this very simple code to duplicate n "brander" symbols:
for (var i:Number=0; i<20; i++) {
var m = new brander("MS_"+i);
addChild(m);
m.name = "MS_"+i;
m.x = 20*i;
m.alpha = a;
a-=0.05;
m.y = 20;
}
The symbol is linked to brander.as class.
The class is this one:
package {
import flash.display.*;
public class brander extends MovieClip {
var n:String;
//
public function brander(name) {
setName(name);
}
//
function setName(name) {
this.n = name;
}
//
function getName() {
return n;
}
}
}
and it is simple too.
Now: I noticed I can't really set anything in this class. So, when I call setName (at the creation of a "brander" instance), I don't set anything. Is this possible?
I tested without debugging, by simply writing:
btn.addEventListener(MouseEvent.MOUSE_DOWN, test);
//
function test(EVT) {
trace(this.getChildByName("MS_2").getName());
}
Why do we link a class when this class can't store information? What am I doing wrong?
EDIT:
I found this is working:
function fun(EVT) {
trace((this.getChildByName("M_2") as brander).getName());
}
but I can't understand WHY: could you please tell me why?
The reason is that the getChildByName() funcction returns a DisplayObject. The DisplayObject has no getName function. The brander class however inherits from (extends) the DisplayObject, and therefore you can store it as a DisplayObject. But if you want to call any of the brander functions, you need to cast it to brander first, using as.
There is lots of information on casting, polymorphism and inheritance several places on the internet.