cocos2d js 3.7 runScene issue - javascript

So i am porting my game from cocos2d c++ to js and sorted lots of js related differences already, but can not understand nor find online the solution to particular problem involving switching of the scenes with the cc.director.runScene().
So i have a complex game scene which i load like that:
cc.LoaderScene.preload(gameplay_resources, function () {
cc.director.runScene(new GameScene(level, epoch));
}, this);
Which takes stage and level params and returns the scene object which contains all the gameplay elements.
Fragment of Game scene looks like this:
var GameNode = cc.Node.extend({
levelID:null,
epoch:null,
platforms:null,
boardSize:null,
numberOfMovesLeft:null,
numberOfMovesAllowed:null,
sessionInfo:null,
ctor:function (levelID, epochID)
{
this._super();
this.levelID = levelID;
this.setEpoch(Epoch.epochForNumber(epochID));
this.platforms = [];
var self = this;
var gameplayUI = ccs.load(gameplayRes.GameplayUI, "res/");
this.addChild(gameplayUI.node);
var replayButton = gameplayUI.node.getChildByName("replay");
cc.assert(cc.sys.isObjectValid(replayButton), "Replay button not valid");
replayButton.addTouchEventListener(function (sender, type) {
if(type != ccui.Widget.TOUCH_ENDED) return;
cc.LoaderScene.preload(gameplay_resources, function () {
cc.director.runScene(new GameScene(self.levelID, self.epoch.ID));
}, this);
}, this);
return true;
}
//... Other methods
});
var GameScene = cc.Scene.extend({
ctor:function (levelID, epochID) {
this._super();
var gameNode = new GameNode(levelID, epochID);
this.addChild(gameNode);
}
});
All works fine in the end in the scene itself, but say if i change the scene by the same method to main menu, and then go to gameplay again, it seems as the 'new' game scene object has previous (supposed to be released) object`s values. So if i press 'Replay' button, it uses the old game scenes values as well.
I mean in C++ when you replace scenes, old scene`s memory is released by cocos engine. And if you go to that scene by creating new object - its new.
So my question is why is that happening i js? Maybe i do not know something about cocos2d js memory management so i need to do something extra to release old scene?
What i have ensured:
When i extend the engines classes all my added variables a null'ed and objects values are assigned in ctor's using this.value = ...; not to have static values for all objects in that way.
There are no children retaining the Gameplay scene (or it seems not to be) so i am assuming it should be released when i replace scene?
I am not using retain()/release(), and all children are added to the gamescene to be retained.
Any suggestions would be appreciated.
Cheers!

So i have solved it - i was using event manager therefore sending events and game scene with its children have been registered for some of them. I was assuming that eventManager retains nodes on a 'weak' basis, but it seems you have to call removeListeners() for each registered node in its onExit method to remove listeners manually. So now it seems to be working fine.

Related

Undo/Redo for leafet routing machine

I am trying to implement simple undo/redo function for my tool which uses leaflet and leaflet routing machine.
Here is my function:
var activityBuffer = [];
var undoFlag = false;
Routing.on('routeselected', function(){
if (undoFlag) {
undoFlag = false;
}
else {
var newWaypoints = Routing.getWaypoints();
activityBuffer.push(newWaypoints);
console.log(activityBuffer);
}
});
function undoActivity(){
var lastStateIndex = activityBuffer.length - 2
if (lastStateIndex >= 0) {
var oldState = activityBuffer[lastStateIndex];
Routing.setWaypoints(oldState);
activityBuffer.splice( activityBuffer.length - 1, 1);
undoFlag = true;
console.log(activityBuffer);
}
}
It works fine if i just add more points and the routeselected event is fired, but problem is when i move my waypoints and the cordinates of same points are changed, the entry in the activityBuffer of that waypoint is also updated on its own, add another array of new waypoints is also pushed. why so?
For example:
I hope i explained my problem.
Looking for some help!
I think the problem is that Leaflet Routing Machine under some circumstances mutate (change) the existing waypoint instances in place, rather than always creating new waypoint instances. For example, when dragging a waypoint, the coordinate of the waypoint is overwritten in the same instance.
Since your activityBuffer saves the references to existing waypoints, those waypoints will also be updated when LRM updates them. Storing copies of the waypoints instead should fix your problem.
Also note that strictly, you should store the waypoints from the route that is passed to your event handler (routeselected) instead of grabbing the control's waypoints - this might be important when network latency is high, for example.

Javascript: Can't handle events for multiple instances

I have put together a fun API for game creation. In the code I create a prototype for Mover and then extend it with several specific prototypes (Gold, Monster, and Hero). Each one is based on a img tag with a given ID. I use type-specific information in the constructor and a single template method for each type. Most of the functional code in Mover depends on those type-specific details. I have included one example for simplicity.
I use method calls in a separate script to create and destroy instances of the Mover child types. When I create and destroy one instance at a time everything works as intended. The image updates, the sound plays and it is removed after the correct delay. If I create two or more, however, only the last one works as expected. So if I make gold, moster, hero. Only the hero will remove correctly. The other two will play the audio, but don't appear to update.
I ran into the same problem when I tried to attach a function to the onclick event for more than one instance. Only the last one worked and the others did nothing. Obviously I'm missing something about the way java handles method assignments. Any explanation you can offer would help.
Thanks,
BSD
function Mover()
{
}
Mover.prototype.InitTag = function()
{
this.HTMLtag.src=this.imageURL;
this.HTMLtag.style.position="absolute";
this.HTMLtag.style.width=characterSize;
this.HTMLtag.style.height=characterSize;
this.Position(Math.floor(Math.random()*(MaxW-characterSize)+(characterSize/2)),Math.floor(Math.random()*(MaxH-characterSize)+(characterSize/2)));
}
Mover.prototype.Destroy = function()
{
var disp = this.HTMLtag.display;
this.HTMLtag.src=this.destroyURL
this.HTMLtag.display = disp;
this.destroyAudio.play();
this.RemoveTag();
}
function Monster(id)
{
this.MonsterID = id;
this.HTMLtag = document.getElementById("monster"+id);
this.imageURL = "monster1.jpg";
this.destroyURL = "monster2.jpg";
this.destroyAudio = monsterAudio;
}
Monster.prototype = new Mover();
Monster.prototype.RemoveTag = function()
{
var mID = this.MonsterID;
setTimeout(function() {field.DeleteMonster(mID)}, 1000);
}
function Hero()
{
this.HTMLtag = document.getElementById("hero");
this.imageURL = "hero1.jpg";
this.destroyURL = "hero2.jpg";
this.destroyAudio = heroAudio;
}
Hero.prototype = new Mover();
Hero.prototype.RemoveTag = function()
{
setTimeout(function() {field.DeleteHero()}, 5000);
}
function Gold(id)
{
this.GoldID = id;
this.HTMLtag = document.getElementById("gold"+id);
this.imageURL = "gold1.jpg";
this.destroyURL = "gold2.jpg";
this.destroyAudio = goldAudio;
}
Gold.prototype = new Mover();
Gold.prototype.RemoveTag = function()
{
var mID = this.GoldID;
setTimeout(function() {field.DeleteGold(mID)}, 1000);
}
---------UPDATE UPDATE UPDATE-----------
I have at least partially fixed the problem. I have gotten it to work, but I still don't know why it didn't function as intended. I noticed that while my browser's (Chrome) developer tools could visually identify the most-recently-added Mover when it was being destroyed, it could not do so with the any other movers.
Tag of most recently added Mover can be identified in Chrome developer tools.
This suggested that Mover.HTMLtag was not actually the same as document.getElementById('mover1'). I was able to confirm this by looking at the variables in the GoldField.DeleteMover. At the line indicated mover.src has not changed, but movers[id].HTMLtag.src has been correctly updated. In the most-recently-added case they were both the same.
GoldField.prototype.DeleteMover = function(id)
{
var isHero = false;
if(this.Hero!=null && id==this.Hero.myID)
{
this.Hero = null;
isHero = true;
}
else if(this.Tower!=null && id==this.Tower.myID)
{
this.Tower = null;
}
var mover = document.getElementById("mover"+id);
if(!isHero)
{
this.tag.removeChild(mover);//<<< HERE HERE HERE HERE
delete this.movers[id];
}
}
So, I changed one line in Mover.Destroy. By finding the tag by ID and setting the src. I was able to reliable behavior. It would appear that Mover.HTMLtag is not reliable the same after the second Mover is added. Any explanation?
Mover.prototype.Destroy = function()
{
document.getElementById(this.HTMLtag.id).src=this.destroyURL;
this.HTMLtag.src=this.destroyURL;//old method
this.destroyAudio.play();
this.RemoveTag();
}
On suspicion that this might extend to other updates to this.HTMLtag I set up some basic movement of the Hero. It works great, but if you add one additional Mover of any kind it no longer moves. That narrows down the question considerably. Why would constructing a second Mover cause the prototype members to change?
So I debug your code and I found the cause of your problem. The problem was when you create a new instance of monster you storing a reference to it on the monster var. And when you delete it you don't delete / update the reference to it. So your delete function myField.DeleteMover(id) try to delete a monster already deleted. How to solve this.
// create an array to keep ref to our instances
var monsters= [];
// var monster = null;
function addMonster()
{
// monster = goldField.AddMonster();⏎
// push every monster in array
monsters.push(goldField.AddMonster());
}
function killMonster()
{
// if array.length is true
if (monsters.length) {
// call the destroy function on the last ref
monsters[monsters.length - 1].Destroy();
// remove the last ref from array using pop
monsters.pop();
}
//monster.Destroy();
}
This is working however I think all of this should be done in the objects itself. And you should not care about it here.
Another advice try to use more array methods. Avoid using delete on array index because it mess with index and count instead use splice(index, 1) same for add item in array use push instead of arbitrary index.
Anyway funny game! Good luck to finish it.
Edit, after your answer I go back an test.
To make it work I do this.
// First go inGoldField.prototype.DeleteMover and replace the ugly delete index by
this.movers.splice(id, 1);
// Then in the Mover.prototype.Destroy
// This part is a a little blurred for me.
// the current HTMLtag looks good but when I console.log like this
console.log('before', this.HTMLtag);
this.HTMLtag = document.querySelector("#mover" + this.myID);
console.log('after', this.HTMLtag);
// They are not equal look like the first is outdated
You should convert all your delete and add to splice and push methods.
This is just a quick debug I don't know why the selector is outdated.
So I check the code again and I make it work without refreshing the selector. The problem is caused by the creation of dom element with innerHTML.
First reset
this.HTMLtag.src=this.destroyURL
Then instead of
//Mover.prototype.Destroy
this.tag.innerHTML+="<img id='mover"+this.moverCount+"'>";
I create a img dom el.
var img = document.createElement("img");
img.setAttribute('id', 'mover' + this.moverCount);
this.tag.appendChild(img);
All Monsters are now deleted with the image.
I don't check for the hero but first you should update your innerHTML and reply if there is still a problem. I don't think there is any problem with some prototype.

$.empty() vs Backbone's View.remove()?

I understand that when a view is removed through .remove(), .stopListening() is called on that view to remove any event listeners associated with that view in Backbone. From the Backbone docs:
remove view.remove()
Removes a view from the DOM, and calls stopListening to remove any bound events that the view has listenTo'd.
I have views that are appended to a container that only have events related to dom actions on themselves through Backbone's events hook.
var View = Backbone.View.extend({
events : {
'input keyup' : 'searchDropdown'
},
searchDropdown: function () {
$('dropdown').empty();
//Appends views based on search
}
});
My question is really whether or not I'm leaking any memory (significant or not) when calling $.empty() on a container that effectively removes the view(s) appended inside of it. And if I am, is there any good convention for accessing and calling .remove() on those views?
You don't need any special framework for this but it's a good idea to implement removal properly and not depend on the browser being smart enough to do this. Sometimes in a large app you will find you specifically need to override the remove method to do some special cleanup - for instance you are using a library in that view which has a destroy method.
A modern browser tends to have a GC which is smart enough for most cases but I still prefer not to rely on that. Recently I came on to a project in Backbone which had no concept of subviews and I reduced the leaking nodes by 50% by changing to remove from empty (in Chrome 43). It's very hard to have a large javascript app not leak memory, my advice is to monitor it early on: If a DOM Element is removed, are its listeners also removed from memory?
Watch out for things which leak a lot of memory - like images. I had some code on a project that did something like this:
var image = new Image();
image.onLoad(.. reference `image` ..)
image.src = ...
Basically a pre-loader. And because we weren't explicitly doing image = null the GC never kicked in because the callback was referencing the image variable. On an image heavy site we were leaking 1-2mb with every page transition which was crashing phones. Setting the variable to null in a remove override fixed this.
Calling remove on subviews is as easy as doing something like this:
remove: function() {
this.removeSubviews();
Backbone.View.prototype.remove.call(this);
},
removeSubviews: function() {
if (!_.isEmpty(this.subViews)) {
_.invoke(this.subViews, 'remove');
this.subViews = [];
}
}
You just need to add your subview instances to an array. For example when you create a subview you could have an option like parentView: this and add it to the array of the parent. I have done more intricate subview systems in the past but that would work fine. On initialize of the views you could do something like:
var parentView = this.options.parentView;
if (parentView) {
(parentView.subViews = parentView.subViews || []).push(this);
}

Better way than mediator pattern for decoupling widgets in this situation?

I’m trying to figure out which pattern to follow in a certain situation. I have web app that consists of several main widgets that interact with each other somehow. The widgets follow the module pattern.
To let code speak:
MyApp.Widgets.MainLeftBox = (function(){
var self = {};
self.doSomething = function(){
var certainState = MyApp.Widgets.MainRightBox.getCertainState();
if (certainState === 1){
console.log(‘this action happens now’);
}
else {
console.log(‘this action can’t happen because of a certain state in My.App.Widgets.MainRightBox’);
}
}
return self;
})();
As you can see, I have tight coupling here. As we all know, tight coupling is evil. (Except when you have found the one and only! ;-))
I know a lot of decoupling can be achieved by following a pub-sub / custom event pattern. But that’s better suited for situations were A starts something and B can react upon. But I have a situation where A starts something independently but needs to check a certain state from B to proceed.
As I’m striving for maintainability, I’m looking for a way out of this hell.
What first came to my mind is the mediator pattern.
But still, my code would look like this:
MyApp.Widgets.MainLeftBox = (function(mediator){
var self = {};
self.doSomething = function(){
var certainState = mediator.getCertainState();
if (certainState === 1){
console.log(‘this action happens now’);
}
else {
console.log(‘this action can’t happen because of a certain state in mediator’);
}
}
return self;
})(MyApp.Mediator);
This is a little better, because Widgets don't communicate directly but indirectly through the mediator.
However, I still feel that I'm doing it wrong and there must be a better way to achieve decoupling the widgets from each other.
EDIT
Let me sum things up so far!
In general, I do like the MVC approach of separating the views! However, think of this example more like complex modules. Those doesn't really have to be "boxes" in a visual sense. It's just easier to describe this way.
Another given fact should be, that A starts an action independently and needs to check for some state then. It can't subscribe to B's state change and provide the action or doesn't. It has to be like A starts it independently and then needs to check a certain state. Think of this as some complex operation that B needs be asked for.
So I came up with a mixture of custom events/callback/mediator approach and there are some things that I really like about it.
1.) A module doesn't know about any other module
2.) A module doesn't know about a mediator neither
3.) A module that depends on some external state does only know that it depends on some external state - not more
4.) A module really doesn't care who will provide this certain state
5.) A module can determine if that certain state has been provided or not
6.) The request pipeline is straight. In other words the module is the starter of this operation. it doesn't just subscribe to a state change event (Remember A starts the action and then needs a state from B (or somewhere)
I posted some example code here and also provide a jsFiddle: http://jsfiddle.net/YnFqm/
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
</head>
<body>
<div id="widgetA"></div>
<div id="widgetB"></div>
<script type="text/javascript">
var MyApp = {};
(function (MyApp){
MyApp.WidgetA = function WidgetA(){
var self = {}, inner = {}, $self = $(self);
//init stuff
inner.$widget = $('#widgetA');
inner.$button = $('<button>Click Me!</button>')
.appendTo(inner.$widget)
.click(function(){self.doAction();});
self.doAction = function(){
//Needs state from WidgetB to proceed
/* Tight coupling
if (MyApp.WidgetB.getState() == 'State A'){
alert('all fine!');
}
else{
alert("can't proceed because of State in Widget B");
}
*/
var state;
$self.trigger('StateNeeded',function(s){state = s});
if (state == 'State A'){
alert('all fine!');
}
else{
alert("can't proceed because of State in Widget B");
}
};
return self;
};
MyApp.WidgetB = function WidgetB(){
var self = {}, inner = {};
//init stuff
inner.$widget = $('#widgetB');
inner.$button = $('<button>State A</button>')
.appendTo(inner.$widget)
.click(function(){
var buttonState = inner.$button.text();
if (buttonState == 'State A'){
inner.$button.text('State B');
}
else{
inner.$button.text('State A');
}
});
self.getState= function(){
return inner.$button.text();
};
return self;
};
MyApp.Mediator = (function(){
var self = {}, widgetA, widgetB;
widgetA = new MyApp.WidgetA();
widgetB = new MyApp.WidgetB();
$(widgetA).bind('StateNeeded', function(event, callback){
//Mediator askes Widget for state
var state = widgetB.getState();
callback(state);
});
return self;
})();
})(MyApp);
</script>
</body>
</html>
You should checkout a great article about large scale JS apps presented by Addy Osmani Patterns For Large-Scale JavaScript Application Architecture and here is a code sample Essential js design patterns
You can still go with the mediator, but implement your business logic inside it. So, instead of mediator.getCertainState(), have a method mediator.canTakeAction() which knows about the widget(s) to query, and determine if the action is allowed.
This will still end up with a mediator which knows the widgets to query, of course. But since we've offloaded the business logic inside the mediator, I think it is OK for it to know of such things. It may even be the entity that creates these widgets. Alternatively, you can use some sort of registration mechanism where you tell your mediator which widget is used for what role when you create them.
EDIT: Providing an example in the spirit of the given code samples.
MyApp.DeleteCommand=(function(itemsListBox, readOnlyCheckBox) {
var self = {};
self.canExecute = function() {
return (not readOnlyCheckBox.checked()) && (itemsListBox.itemCount() > 0);
}
return self;
})(MyApp.Widgets.ItemsList, MyApp.Widgets.ReadOnly);
You can take this two steps further:
Register to state changed events of the source widgets, and update a local cache of the canExecute every time a state change occurs on one of the source widgets.
Also take a reference to a third control (say, to the delete button), and enable or disable the button according to the state.
Assuming I'm understanding the nature of a "box" as a box that's visible on your page, then the box should render a view that represents a state of your application or some piece of it -- the underlying state itself should be maintained by an object that's separate from the view that represents that state in the UI.
So, for example, a box view might render a view of a Person, and the box would be black when the person was sleeping and white when the person was awake. If another box on your was responsible for showing what the Person was eating, then you might want that box to only function when the person was awake. (Good examples are hard and I just woke up. Sorry.)
The point here is that you don't want views interrogating each other -- you want them to care about the state of the underlying object (in this case, a Person). If two views care about the same Person, you can just pass the Person as an argument to both views.
Chances are good that your needs are a tad more complicated :) However, if you can think about the problem in terms of independent views of "stateful objects", rather than two views that need to care directly about each other, I think you'll be better off.
Why can't you use pub-sub model in the following way
LeftBox issues a getStateFromRightBox event.
RightBox has getStateFromRightBox subscriber, which, issues sendStateToLeftBoxAndExecute event with the stateData
LeftBox has a sendStateToLeftBoxAndExecute subscriber which extracts stateData and executes the action conditionally.
A Few Potential Options
I would still recommend using a Mediator -- however, if you're more of an Inheritance fan, you may want to play around with the Template Method, State or Strategy, and Decorator Patterns -- since JavaScript does not have interfaces, these might be useful.
The latter approach might allow you to categorize your procedures into more manageable strategies, however, I'll go on to cover the Mediator since it makes the most sense [to me] in this situation.
You can implement it as EDM (Event-Driven Mediation) or as a classic Mediator:
var iEventHub = function iEventHub() {
this.on;
this.fire;
return this;
};
var iMediator = function iMediator() {
this.widgetChanged;
return this;
};
The only thing I can really advise is to break down your procedures to give Mediator a chance to have a say during the process. The mediation could look more like this:
var Mediator = function Mediator() {
var widgetA = new WidgetA(this)
, widgetB = new WidgetB(this);
function widgetChanged(widget) {
identifyWidget(widget); // magical widget-identifier
if (widgetA.hasStarted) widgetB.isReady();
if (widgetB.isReady) widgetA.proceed("You're proceeding!");
}
return this;
};
var WidgetA = function WidgetA(director) {
function start() {
director.widgetChanged(this);
}
function proceed(message) {
alert(message);
}
this.start = start;
this.proceed = proceed;
return this;
};
var WidgetB = function WidgetB(director) {
function start() {
this.iDidMyThing = true;
director.widgetChanged(this);
}
function isReady() {
return iDidMyThing;
}
this.iDidMyThing = false;
this.start = start;
this.isReady = isReady;
return this;
};
Basically, WidgetA has to get permission from Mediator to proceed, as Mediator will have the high-level view on state.
With the Classic Mediator, you'll likely still need to call director.widgetChanged(this). However, the beauty of using EDM is that you don't necessarily couple to Mediator, itself, but all modules implement an iEventHub interface or couple to a common hub. Alternatively, you can modify the classic Mediator to aid in Module Authorization by refactoring the widgetChanged method:
// Mediator
function widgetChanged(ACTION, state) {
var action = actionMap[ACTION || 'NO_ACTION_SPECIFIED'];
action && action.call && action.call(this, state);
}
// WidgetX
const changes = this.toJSON();
director.widgetChanged('SOMETHING_SPECIFIC_HAPPENED', changes);
I think you're very close -- I hope this helps.

Why is 'this' not updating to refer to a new object?

I'm writing an online game which allows a user to progress from one puzzle to the next, and if the user makes mistakes, each puzzle has a start again button to allow the user to start just that puzzle from scratch. A simplified version of the code's structure is below:
function puzzle(generator) {
this.init = function() {
this.generator = generator;
...
this.addListeners();
}
//fires when the puzzle is solved
this.completed = function() {
window.theSequence.next();
}
this.empty = function() {
//get rid of all dom elements, all event listeners, and set all object properties to null;
}
this.addListeners = function() {
$('#startOver').click(function() {
window.thePuzzle.empty();
window.thePuzzle.init();
});
}
this.init();
}
function puzzleSequence(sequenceGenerator) {
this.init = function() {
//load the first puzzle
window.thePuzzle = new puzzle({generating json});
}
this.next = function() {
//destroy the last puzzle and create a new one
window.thePuzzle.empty();
window.thePuzzle = new puzzle({2nd generating json});
}
}
window.theSequence = new puzzleSequence({a sequence generator JSON});
The problem I have is that if the user has progressed to the second puzzle, if they click start over it loads the first puzzle rather than the second. After a bit of debugging I've worked out that 'this', when used in methods by the second puzzle, for some reason still holds a reference to the first puzzle, but 'window.thePuzzle' - which should be the same as this - correctly refers to the second puzzle.
Why is 'this' persisting in referrring to the first one?
Let me know if you need more code samples
$('#startOver').click(this.empty);
You've taken the empty method and detached it from this to pass as a plain unbound function to jQuery. When it gets called back, it will have no reference to the original value of this. In fact, when a function is called unbound, this will refer to window, so you'll be scribbling what you think are properties onto the globals.
JavaScript doesn't bind methods in the same way as other languages. See eg. this answer for an explanation of what it actually does. This confuses many people; personally I consider it one of JavaScript's worst flaws.
There is a very good (and clear) description of exactly how the this reference is treated in different contexts over at Quirksmode.

Categories

Resources