|
nahkranoth
|
 |
« on: April 15, 2011, 04:42:20 AM » |
|
Hello fellow TIGers, I am busy with a game in Flash AS 3.0 and came quiet far allready on my own check here, but now i need help. The problem is that i have never really had such a big project and i'm kind of new to AS 3.0 and OOP.(have lots of experience in AS 2.0). The problem occurs when i want the player to die and go back to the beginscreen. When i load the second level, it leaks memory like hell, keeps every block builded on the screen and so on as you can see here. Can someone help me with the logic behind coding in classes and how you structurize them. Let's say i have these files/classes: - World.fla - World.as - Enemy.as - Bullet.as - Building.as I want the enemy to shoot a bullet that when is hitting a building, makes the building disappear and the bullet. Now i code it like this pseudo code: World.as public static var ENEMY:Enemy; public static var BUILDING:Building; public static var BULLET:Bullet;
World.as say -> Enemy.fire(); Enemy.as fires -> new Bullet created. Bullet.as checks via enterFrame -> did i hit world.BUILDING Bullet.as If i did destroy world.BUILDING and world.BULLET
But i have the feeling it should be done in another way. More Event based. But how does that logically works? Because now i'm havind a lot of problems to remove all the references and eventlisteners connected to world.BULLET and world.BUILDING Or am i just freaking out over nothing and is this a logic way to build it?
|
|
|
|
|
Logged
|
|
|
|
|
Richard Kain
|
 |
« Reply #1 on: April 15, 2011, 06:52:57 AM » |
|
Well, part of your problem is the rampant use of the "static" modifier. You should probably go a little easier with that. Abuse of the static modifier can come back to bite you in the ass if you aren't careful. Most importantly, you don't NEED to use the static modifier unless you need to make changes to a particular variable/object from ANYWHERE. If the majority of your game logic is stored in your World class, you don't need to define your enemy, bullet, or building objects as static.
You also seem to be confusing "classes" and "objects." This is a common rookie mistake when it comes to OOP. A class is a blueprint, a template for something that does not yet exist. In AS3, classes take the form of individual .AS files. (it is actually possible to define more than once class in an .as file, but don't try it) An object is an instance of a class. The created object will contain all of the properties, data, and functions of the class. But once it is created, you will be able to change the various values of its properties so that it is different from other instances (objects) of the same class.
And here's where objects, classes, and the static modifier overlap. When you define a variable in a class as being "static," that means that particular variable will never be different in separate objects. Lets say you write a class. You make a Boolean variable in it named "Friendly." You set it to "true" and then you define it as being static. Then you use that class and create three different instances. By default, all of their "Friendly" variables are set to "true." You change one of their Friendly variables to "false." Because that variable was static, it's value changes for every instance. So all three of your objects will have the value of their "Friendly" variable switched.
The static modifier is powerful, and very useful for exposing access to specific variables. But you should use it cautiously. It is best used when you are certain that there is only ever going to be one instance of a specific object and/or variable. I usually keep my static variables in whatever the root class for my program is.
|
|
|
|
|
Logged
|
|
|
|
|
nahkranoth
|
 |
« Reply #2 on: April 15, 2011, 08:39:06 AM » |
|
Verry helpfull, Richard. I start to see where i made errors. But i'm not yet sure how i can solve it.
I think it comes down on rewriting the entire code and making sure i put everything in the place it should be. But first i think i need some extra research on how i put everything in the right place. Back to the drawing board i guess.
|
|
|
|
|
Logged
|
|
|
|
|
Richard Kain
|
 |
« Reply #3 on: April 15, 2011, 09:24:18 AM » |
|
One of the things I like to do with Flash is to use my main "entry" class for setting things up, and then handle actual game logic within my "scene" class. I will create a class that I usually name "scene." You can name it "state" or "level" or whatever, as long as it makes sense to you. Then I will extend classes from the base "scene" class in order to construct each individual game "state." So my main menu class will be an extension of the scene class, and will contain all the logic necessary for a main menu. This is an instance where a static variable could actually come in handy. When it comes to changing scenes, you want to be able to do that from almost anywhere in your game. I will usually create a "currentScene" variable within my main entry class, and set that as static. After all, I will never really need more than one scene running at a time, that's the whole point of breaking the program up into different scenes. Whenever I want to change the scene, I can assign any object that extends the scene class to the "currentScene" static variable. I will normally do that like this... MainClass.currentScene = new PlayScene(); //where PlayScene is a class that extends the base "scene" class
This assigns an instance of the PlayScene class to the currentScene variable. Another prime candidate for the static modifier is your elapsed time variable. Using an elapsed time variable is an excellent way to base your animations and in-game interactions on time intervals as opposed to frame intervals. In most programming, there is a natural division between the two, with a separate loop for Updating game logic and Rendering frames. In flash this is not the case. The "ENTER_FRAME" event is the only regular update loop you have. Because of performance, it is better to only use it once. (usually in the main entry class) You can use the built-in Flash timer functions to calculate how much time has elapsed since the last frame was rendered, and then assign that value to a variable. Calculating that value multiple times each frame would be costly depending on how many objects try to figure it out, so it is better to only do it once. If you assign the elapsed variable as being static, you can access it from any other point in your program. It's also a good idea to make it read-only, as you don't want to change it, just reference it.
|
|
|
|
|
Logged
|
|
|
|
|
Triplefox
|
 |
« Reply #4 on: April 16, 2011, 01:37:47 AM » |
|
I take a different approach with managing the game state in Flash - instead of doing everything in one giant object, I create a bunch of classes that hold various subsystems, instance them in a few static variables, and create a reset() function that manages the lot.
The main reason to do so is because of the Flash scenegraph and events - if you just new() over your old game state, you haven't cleaned the scenegraph or the events, and that causes a leak. (You can be stubborn and only use draw() and copyPixels() in your game and only instance a single clicker object and never touch the Flash rendering or event APIs in any other way, achieving a fully static initialization that you can, in fact, new() over, but you're really fighting an uphill battle to do that. Believe me, I tried.)
But I also like doing it in this way because it reduces typing a bit and keeps the code structured pretty flat. It's easy to go overboard with structure when you first buy into the idea of building a hierarchy of objects - the software engineering literature of the last 20 years has screamed at everyone to do things in this way.
Most of the time you want a flat layout in a game, though, because anything in the game could potentially affect anything else, and you can (usually) make a case for there only being one of that thing there. The architectural jump from "one thing" to "more than one thing" is a huge one - and you almost never get it for free just by trying to design the data that way up front, because some additional considerations in the code it interacts with will always appear. For me it works out better to code things up in a heavily static way, because it's easier to refactor later than it is to tear out bad guesswork architecture.
All of this practical contradiction with the literature has made me suspicious of doing much with classes - I like treating them as typed bags of data, with a few public methods where they're handy, and some static methods for external things they're associated with, but unless I'm making an API, I don't use private variables, and I rarely inherit anything.
The entities in my current game are anonymous objects, so I have a lot of static methods to deal with them, and making them anonymous gives me room to swap things in and out as the design changes - I find myself constantly dumping more one-off variables on stuff. The typed classes are instances of subsystems like collision or rendering, and the entities themselves only hold id references to those instances.
That said, I let a little bit of hierarchy form these days by starting from relatively "raw" classes like a tilemap or collision manager, and instancing them onto a "World" class as static variables.
Another point the OP touched on is worth coming back to - events. The events in Flash aren't very useful for games. They help with wiring up the GUI when you have buttons and sliders and such, but when we're talking about game events, we want to do things with timing and ordering - take this use case, for example:
In your game, you have a row of bombs resting near each other. A bullet collides with one of the bombs, setting off a chain reaction where all bombs explode in the same frame.
This is tricky - a lot of the time, you can get away with almost everything in a game being an instantaneous effect, one function call away, and most of them being wired into your collision in some way. But then you have one quirk like this and things go haywire.
To accomplish this, you have to set up a loop of some kind that processes a queue of events (your own event objects, not the Flash ones) one by one, allowing the queue to cascade events as necessary, until the queue is cleared, at which point the frame ends. So your event queue might start out looking like:
(input,ai,collision,render)
and at "collision" the bullet hits, and we have to do something to maintain the chain reaction. Why not make a new collision event just to handle these, and call it "collision2"?
(collision2,render)
Then, as long as there's a chain reaction, we just push another collision2 onto the front.
Of course, events can be further architected - filing the stages of event processing into "buckets" instead of one big queue, or adding more timing information so that delayed events - both in "wall-clock" time and "game" time - can occur without coding up new timers for each and every one. It's just a matter of what the game needs.
|
|
|
|
|
Logged
|
|
|
|
|
nahkranoth
|
 |
« Reply #5 on: April 16, 2011, 05:13:10 AM » |
|
Their is a lot of information i have to read again, but i have the feeling it all starts to make sense. Their should be a instance "world" that controls every other instance, so the collision detection for "bullet" and "building" are handled by the "world" instance. This way i don't have to make bullet or building static, because they are a part of world, and i can acces them through there. And delete them if necessary.
Triplefox, you are saying that i could also make make subclasses too handle for instance the collision detection. I like the idea, because i'm afraid the World file will clutter up. If i let the collision detection also be part of World, then there is no problem.
You'r also talking about the timer, but i have to get deeper into that, i'm now running on EnterFrame.
One more question about cleaning up: If i made the "bullet" instance in "World" and i have checked a collision, can i just delete the "bullet" by removing the instance and the child from the stage. (Offcoarse allso any eventlisteners connected to the instance)? Or do i need to do more to effectifly remove it from the memory
|
|
|
|
|
Logged
|
|
|
|
|
X-Tender
|
 |
« Reply #6 on: April 16, 2011, 06:43:07 AM » |
|
Its enough to remove it from the stage (ans eventlisteners). Also, if it is an animated Movieclip you must stop it. Otherwise it wont be removed from Memory.
|
|
|
|
|
Logged
|
|
|
|
|
Triplefox
|
 |
« Reply #7 on: April 16, 2011, 12:14:58 PM » |
|
If the only thing representing the bullet is the DisplayObject then it should work to remove it from the stage.
There is one caveat to look out for when you spawn or despawn anything - if you're doing a loop over a list of all bullets, for example, and you want to remove one(because it's lifespan ended or it touched a wall or something), it's safest to defer actually removing the bullet from the list until you've finished processing all the bullets - by making some kind of "list of bullets to remove." Otherwise you have to adjust the loop iterator at removal time so that it doesn't skip stuff or go past the end of the list, which is hard, but also, it isn't just a problem with the one loop:
If you have your state distributed in the way I suggested(multiple subsystems holding pieces of the entity) you'll also have multiple passes of processing going on, and thus the interactions will be occurring across multiple loops(one for physics, one for ai, etc.) - and they'll rely on information from the previous pass, so you'll have to make sure that you're at a "safe" point to despawn stuff, or entities will go looking for information you just destroyed.
Spawning new objects can pose a similar problem depending on how you're initializing stuff, but in practice it only causes me grief if there's a potentially recursive situation like the bomb chain-reaction one.
|
|
|
|
|
Logged
|
|
|
|
|
ishlilith
|
 |
« Reply #8 on: April 16, 2011, 07:03:52 PM » |
|
Its enough to remove it from the stage (ans eventlisteners). Also, if it is an animated Movieclip you must stop it. Otherwise it wont be removed from Memory.
Removing it from the stage wont GC it, you must null it.
|
|
|
|
|
Logged
|
|
|
|
|
X-Tender
|
 |
« Reply #9 on: April 17, 2011, 12:19:31 AM » |
|
well, thats half true. when you have a variable which points at an object and you set the variable null the object still exists. That's why I don't use NULL pretty often because it takes sometimes more work just to release a bit of memory. I use it only for memory intensive data like bitmapdatas and sounds.
|
|
|
|
|
Logged
|
|
|
|
|
nahkranoth
|
 |
« Reply #10 on: April 17, 2011, 02:49:38 PM » |
|
Just to check is this the right way to make and delete an instance? Here i make a square: private function createSquare() { var square:Square = new Square(); addChild(square); square.x=100; square.y=300; square.addEventListener(MouseEvent.MOUSE_DOWN,mouseDowner); } Here i put it in a deleting array. private function mouseDowner(e:MouseEvent) { objRemoveArr.push(e.target);
} And here i delete the instances up for removing: private function removeChilds() { for (var i=0; i<objRemoveArr.length; i++) { removeChild(objRemoveArr[i]); objRemoveArr[i].removeEventListener(MouseEvent.MOUSE_DOWN,mouseDowner); objRemoveArr[i].stop(); objRemoveArr[i]=null; objRemoveArr.splice(i,1); } objRemoveArr=[];
} If i test this and look at memory usage i only see increase, so i'm doubting this works.
|
|
|
|
|
Logged
|
|
|
|
Celery
Level 0
|
 |
« Reply #11 on: April 17, 2011, 04:48:10 PM » |
|
Your problem is in removeChilds. Avoid modifying a container while you are iterating over it unless you're sure it doesn't mess up your logic. Here you visit elements 0, 1, 2, etc. But each time you use splice(1) to remove element 0, and shift down all later elements. So the position 1 you wanted to visit is now at 0 and you skip over it. So half of the squares are left on the stage. private function removeChilds() { for (var i=0; i<objRemoveArr.length; i++) { removeChild(objRemoveArr[i]); } objRemoveArr.splice(0); // empty the array (or do = []) } There's no need to detach things from a dying object, or to clear elements of an array which is getting emptied anyway.
|
|
|
|
|
Logged
|
|
|
|
|
nahkranoth
|
 |
« Reply #12 on: April 17, 2011, 11:22:20 PM » |
|
Ok, that makes sense. Kill it after i'm done is enough.
I did it because i was afraid that keeping the reference alive in the array kept the MC in memory.
|
|
|
|
|
Logged
|
|
|
|
|
nahkranoth
|
 |
« Reply #13 on: April 18, 2011, 03:13:40 PM » |
|
I feel verry stupid for asking the same question again. But i just don't seem to get it working. The memory still keeps rising if i remove the images and add them again. This is the root Class: And there is a Movieclip in the library with class dazzleBoatIMG public class dazzleBoat extends MovieClip{ public var dazzleArr:Array; public function dazzleBoat(){ dazzleArr = []; addEventListener(Event.ENTER_FRAME,skipFrame); btn.addEventListener(MouseEvent.CLICK,remove); btn2.addEventListener(MouseEvent.CLICK,addNew); } public function remove(e:MouseEvent){ for(var i=0;i<dazzleArr.length;i++){ removeChild(dazzleArr[i]); } dazzleArr = []; } public function addNew(e:MouseEvent){ var dazzlio:dazzleBoatIMG = new dazzleBoatIMG; dazzlio.x = 200; dazzlio.y = 200; dazzleArr.push(dazzlio); addChild(dazzlio); } public function skipFrame(e:Event){ for(var i=0;i<dazzleArr.length;i++){ var currDazz:dazzleBoatIMG = dazzleArr[i]; currDazz.x += 1; } }
|
|
|
|
|
Logged
|
|
|
|
|
Glaiel-Gamer
|
 |
« Reply #14 on: April 18, 2011, 03:50:50 PM » |
|
dazzleArr = []; is not how you should be making arrays dazzleArr = new Array() is much better Flash's garbage collector runs when it feels like. If you wanna force it to run to see if it is working, there's a GarbageCollector.run() function that only works in debug mode. For release mode you need to do a localconnection hack where running this code forces the garbage collector to run try { new LocalConnection().connect('foo'); new LocalConnection().connect('foo'); } catch (e:Error) {}
|
|
|
|
|
Logged
|
|
|
|
|
Richard Kain
|
 |
« Reply #15 on: April 19, 2011, 10:01:59 AM » |
|
Flash's garbage collector runs when it feels like. If you wanna force it to run to see if it is working, there's a GarbageCollector.run() function that only works in debug mode.
I have never made any serious effort to control the garbage collection in the Flash player. For the most part, I've never designed a program where it seemed to be necessary. SHOULD I be attempting to control low-level memory management in my Flash applications? Or is it generally safe to let Flash's built-in garbage collection handle that task? We are assuming here that I am not making graphically intensive programs or complex visualization simulations.
|
|
|
|
|
Logged
|
|
|
|
|
Glaiel-Gamer
|
 |
« Reply #16 on: April 19, 2011, 10:07:32 AM » |
|
I have never made any serious effort to control the garbage collection in the Flash player. For the most part, I've never designed a program where it seemed to be necessary. SHOULD I be attempting to control low-level memory management in my Flash applications? Or is it generally safe to let Flash's built-in garbage collection handle that task? We are assuming here that I am not making graphically intensive programs or complex visualization simulations.
No you shouldn't be controlling it, what I was saying is looking at memory use is pointless and if you want an accurate result you need to force the garbage collector to run. Even then, you don't know how much memory flash is gonna keep allocated after its technically "freed", so its not a good way to judge anyway.
|
|
|
|
|
Logged
|
|
|
|
|
nikki
|
 |
« Reply #17 on: April 19, 2011, 10:41:01 AM » |
|
for(var j=0;j<dazzleArr.length;j++) { removeChild(dazzleArr[j]); }
dazzleArr = [];
if you iterate over the array backwards for(var i = dazzleArr.length; i>0; i--) you wouldn't have the same trouble as are being described by Celery, you'd be removing the child you want.. dazzleArr = [];
is not how you should be making arrays
dazzleArr = new Array()
is much better why would that be better ? (isn't it just exactly the same result with less typing ??)
|
|
|
|
« Last Edit: April 19, 2011, 11:06:16 AM by nikki »
|
Logged
|
|
|
|
|
BadgerManufactureInc
Guest
|
 |
« Reply #18 on: April 19, 2011, 11:30:32 AM » |
|
Although most modern OO languages such as AS3 do provide garbage collection, which runs whether you want it or not, it does not mean that you cannot optimise. With regards to the bullets, I would heartily recommend making use of an object pool. Here is how it works: In the point of entry / main class (perhaps 'World' in your list), you can populate a bulletPool array: for(i=0; i<MAX_BULLETS; i++) // set const. max bullets to something like 20 { bulletsPool[i]=new Bullet(-100,0); }
Also create a ' currentBullet' variable, and initialise it to 0. Then when the player presses shoot: public function shoot():void { bulletsPool[currentBullet].x=player.x; bulletsPool[currentBullet].y=player.y; if(player.dir==LEFT) bulletsPool[currentBullet].xv=-bulletSpeed; if(player.dir==RIGHT) bulletsPool[currentBullet].xv=bulletSpeed; bulletsPool[currentBullet].yv=0; currentBullet++; if(currentBullet>MAX_BULLETS) currentBullet=0; }
This way the program creates all it's new bullets in advance and just cycles through all the available bullets during run time. If a bullet hits a building or enemy, just move it off screen. This may not be comprehensive, but should hopefully go some way to illustrate the ease of implementation of object pools. The benefit is that you are never instantiating in real time, which really does make gameplay smoother. Hope that made sense and helps some  I would also checkout Flixel and Flashpunk if you want to see 2 examples of very well put together frameworks, but here's a w.i.p. screen grab of my object classes for my new framework just in case you need some more reference: 
|
|
|
|
« Last Edit: April 19, 2011, 11:41:51 AM by BadgerManufactureInc »
|
Logged
|
|
|
|
|
nahkranoth
|
 |
« Reply #19 on: April 19, 2011, 12:07:19 PM » |
|
Thank you for all your help guys! I downloaded a debugger (monsterdebugger) and checked the memory again. Now i finally see when the garbage collector is put into work with a big dip in the memory usage from time to time. I was freaking out over nothing... but learned a lot! @Badger: Sounds like a really good way to make sure that no lost bullets start leaking memory. Also thanks for that file structure. I allready downloaded a tetris source from somewhere that i pulled out a lot of information about a solid structure. In the end i would say that a lot of trouble was caused by not understanding the difference between a class and a object. I think i got it into my head now. Class is cookie cutter / Object (or Instance) is the cookie. Jumm Jumm 
|
|
|
|
|
Logged
|
|
|
|
|