00:00
00:00
PsychoGoldfish
I like turtles

Age 47

NG Code Monkey

Hard Knocks

Ur Mom

Joined on 9/26/00

Level:
14
Exp Points:
2,010 / 2,180
Exp Rank:
31,563
Vote Power:
5.60 votes
Audio Scouts
2
Art Scouts
3
Rank:
Town Watch
Global Rank:
47,834
Blams:
48
Saves:
131
B/P Bonus:
2%
Whistle:
Normal
Trophies:
57
Medals:
788
Supporter:
11m 30d

Wick Editor Alpha Thoughs/Experiments

Posted by PsychoGoldfish - May 24th, 2019


I've been playing with the Alpha version of Wick Editor off and on since the Wick Editor Game Jam announcement. Out of the gate, it had a lot of deal-breaking issues. Thankfully @luxapodular and @zrispo are very serious about making this game jam a great experience and have really stepped up.


They have been busting ass for the past week to get the Alpha Version up to stuff for the game jam. It's still not quite as intuitive as the other version, but if you are just starting with Wick, I would recommend sticking to the Alpha version because the other one will not have a lot of support going forward. The fixes they have released have changed the Alpha build from being unusable as a game development tool to being just good enough to make some fun, innovative stuff.


That said, there are still a lot of things the older "Live" version does better, so they've lifted the restriction on having to use the Alpha version for the game jam. They also extended the deadline to July 7 to make up for the rough launch.


There are a few more major updates that coming (as of the time of this post), but it's updated enough to at least start learning the basics. @luxapodular released a great tutorial video that should get most people started: https://www.youtube.com/watch?v=cvANBF43KsY&feature=youtu.be


If you've been keeping up with the Alpha's development, or breezed through the tutorial, you may be interested in some of the more advanced concepts and lessons I've come up with.


Helpful Hints


"this" context


If you read my last news post, I went into detail about how the "this" context works in Wick. The Alpha version handles scoping pretty much the same way. If you edit code on a frame, 'this' refers to the frame. If you edit a script on an interactive clip, "this" refers to the clip. If you edit a frame within an interactive clip's internal timeline, "this" still refers to the frame. To control the clip in that case, you can use "this.parentClip".


"script execution order"


When working with different scripts on different objects and layers, it's really important to understand the order in which they will execute.


Execution starts with your top layer, and works down. Each frame will execute it's 'Default' script, then it's 'Load' script, before moving down to the next frame.


Once each frame has called it's scripts, any scripting on clips will fire next. Again this will execute from the top layer down. First, a clip will execute it's 'Default' script, then it's 'Load' script. If your clip's inner timeline has any frame scripting, this will fire next, in the same order your root frames did. Once everything inside your clip has executed, the next clip will have it's scripts executed.


The 'Update' scripts will not start on any frame or clip until the last 'Load' script has completed. 'Update' execution works in the same recursive top-down order as the previous scripts did.


All other scripts are event based, and pretty self-explanatory.


Always be mindful of the execution order. If you create any global objects or methods multiple clips and frames will be calling in their 'Default' or 'Load' events, make sure you have defined them in a higher layer!


"Live preview quirks"


One of the neat features in the Alpha version is that you can test your game from any frame without having to start from the beginning. This is because the editor IS actually your game, it's just not firing any events until you hit the play button to test it.


One unintended side effect of it working this way is that any global values or clip propertied you set, stay set even after you stop testing your game.


If you start a new project, and put the following code on frame 1's Default script:

if (globalValue) {
  
  alert('globalValue is already set');

} else {

  alert('globalValue has been created');
  globalValue = 1; // note that I am only setting it if it doesn't already exist

}


The first time you test your movie, you will get an alert saying "globalValue has been created". The next time you test, you will get an alert saying "globalValue is already set".


Also note: If you ran this test, and opened another .wick file without refreshing the browser, globalValue would still be set.


If you reload the browser and start a new project, you can also see another way this quirk can be a problem.


On frame 1's Default script, pop this code in:


HelloWorld = "Hello you sexy Newgrounds fans!";

DO NOT test your game yet. Make a new keyframe at frame 2 and put the following:


alert(HelloWorld);
stop();

While you are still on frame 2, test your game. You will get an alert that says "undefined". This is because you haven't executed the scripting on the first frame yet, so HelloWorld is not defined.


If you go back to frame 1 and test, it will all work as expected. You can also go to frame 2 and test again, and since HelloWorld has been set globally, it will also work.


Start another new project. On frame 1, draw a circle and make it into an interactive clip. On the clip's Default script, put the following:


alert(this.internalValue);
this.internalValue = "Testing 123";

The first time you test your movie, you will get an alert saying 'undefined'. But after that alert, your clip will have an 'internalValue' property set on it, and the alert will say "Testing 123".


If you use global variables or add properties to Wick objects, you need to be very aware of this quirk when testing. The Wick team is planning on adding a way to test your project in a new, sandboxed instance, like the old Live version did. Until then, make sure your scripts are defining all your properties, and that you test your game from whatever frame you need to have everything reset properly.


And for the love of god, do NOT use SetInterval loops right now. They will keep running after you test your game, and if you aren't clearing them anywhere, they each time you test you will add another loop on top of the one that's already going.


Asset Management


In my last post, I also shared some experimentation I did with ways to manage assets and minimize memory while doing so. I've been playing with the same concepts on the Alpha version, and have another example of how you might build an asset library. This time I incorporated clip recycling.


In my experience, there is always some cpu overhead whenever an engine like this creates an instance of a game object. This example lets you store clips in recycle bins so when you need to use them again, they are already created.


Just like last time, you'll want to make a Default script that handles all your assets on frame 1 of your top layer. Here's the script I came up with (full of comments about what everything does). If you don't really care how it works, you can just copy/paste it and scroll down for examples of how to use it.


// This object will handle our clip library
library = {
  
  // this is the total number of each clip type that can be stored
  // in the recycle bin.
  max_recycled: 10,




  // all our clips and their meta info will be stored here
  items: {},




  // clears everything out of the library
  flush: function(name) {
    if (name) {
      delete library.items[name];
    } else {
      library.items = {};
    }
  },
  
  // clears recycled clips from the library.
  flushRecycled: function(name) {
    if (name) {
      library.items[name].recycle = [];
    } else {
      for(var i in library.items) {
        library.flushRecycled(i);
      }
    }
  },
  
  // adds a clip to the library (or returns false)
  add: function(name,clip) {
    
    // all clips will try calling this method by default
    // so this will return false to let the calling clip know
    // it is not the master copy.
    if (typeof(library.items[name]) !== typeof(undefined)) return false;
    
    // if we get here, this is the master copy. We need
    // to store it and create a recycle bin for it's type.
    library.items[name] = {clip:clip, recycle:[]};
    
    // and now this clip no longer needs to be on our stage at all.
    clip.remove();
    
    // return true so the calling clip knows it is the master copy.
    return true;
  },
  
  // removes a clip from the stage and stores it in the
  // appropriate recycle bin (if there's room) so it can be reused.
  // Note: clips will have a native recycle() method added to them
  // via addToFrame, so you shouldn't need to call this directly.
  recycle: function(name,clip) {
    
    // can't recycle things with no library records
    if (typeof(library.items[name]) === typeof(undefined)) {
      // just remove it
      console.warn('Attempted to recycle a clip with no matching library record: ', name);
      clip.remove();
      return false;
    }




    // used to tell the calling script if this was actually added
    // to the recycle bin
    let recycled = false;
    
    // Put it in the recycle bin if there is room
    if (library.items[name].recycle.length < library.max_cached) {
      
      // if clip has a recycle event handler, call it now.
      // I do it here in case you need to access the parent frame or
      // anything before the clip gets removed.
      if (clip.___custom_events.recycled) clip.___custom_events.recycled.call(clip);
      library.items[name].recycle.push(clip);
      recycled = true;
    }
    
    // remove the clip.
    clip.remove();
    
    return recycled;
  },
  
  // gets a copy of the clip and adds it to the desired frame.
  // if force_new isn't true, will attempt to use a recycled copy.
  addToFrame: function(name, frame, force_new) {
    
    // can't add clips that aren't in the library
    if (typeof(library.items[name]) === typeof(undefined)) return;
    
    let clip;
    
    // see if we can use the recycle bin...
    if (!force_new && library.items[name].recycle.length > 0) {
      clip = library.items[name].recycle.pop();
      
    // create a new copy of the clip
    } else {
      
      // Clone the clip. Note: clip must be on a frame to clone it
      frame.addClip(library.items[name].clip);
      clip = library.items[name].clip.clone();
      library.items[name].clip.remove();
      
      // tell the clip what it's named in the library
      clip.___libname = name;
      
      // add a native recycle method to the clip
      clip.recycle = function() {
        library.recycle(this.___libname, this);
      };
    }
    
    // add the clip to the frame
    frame.addClip(clip);
    
    // if the clip has an added event handler, call it now
    if (clip.___custom_events && clip.___custom_events.added) clip.___custom_events.added.call(clip);
    
    // return the clip reference
    return clip;
  },
  
  // This method lets you apply methods from a single object, to
  // any copies of your clip. It should help keep memory optimized and
  // let you organize your code however you prefer.
  applyMethods: function(clip, methodObject, onEvent) {
    
    // if this is a recycled clip, we can skip all of this.
    if (clip.___custom_events) return;
    
    // we have a few custom methods that aren't native to Wick
    // this sets those up.
    let custom_events = ['added','recycled'];
    clip.___custom_events = {};
    
    // check our methodObject for all it's values
    for(var i in methodObject) {
      
      // if the value is 'extend', we treat it like a
      // class extender and just apply all the values verbatim.
      if (i === 'extend') {
        for(var e in methodObject[i]) {
          clip[e] = methodObject[i][e];
        }
      // if the value matches one of our custom events, store
      // it in our custom object
      } else if (custom_events.includes(i)) {
        clip.___custom_events[i] = methodObject[i];
      
      // anything else, we just assume is the name of a
      // native Wick event, and set up an onEvent handler
      } else {
        onEvent(i, methodObject[i]);
      }
    }
    
    // This clip will already be on the stage, so we call
    // the 'added' handler (if it has one)
    // to ensure it gets initialized properly
    if (clip.___custom_events.added) clip.___custom_events.added.call(clip);
  }
};

To use the library script, you need to create an interactive clip to represent a game object, and a JavaScript object containing any code your asset needs. You can simply code for the baked in Wick events like 'update' and 'mouseclick', or use the 'added' and 'recycled' events that the above library script will add to the mix.


Here's an example JS object that will make a clip start at the left side of the screen, move right, then remove itself when it reaches the end of the screen. Just for fun, we'll also make it remove if we click on it.


FlyingThing = {
  
  // We could use Wick's "load" event, but that will only fire the first time a clip is
  // created.  "added" fires any time the library adds the clip to a frame.
  added: function() {
    this.x = 0; // start on the left
    this.y = root.height / 2; // center vertically
  },

  // this will use Wick's baked in 'update' event
  update: function() {

    this.x += 20;

    // check if the clip has reached the end of the stage...
    if (this.x > root.width) {
      // Our library script adds a recycle method to any clips it creates!
      // This will remove the clip from the stage, but keep it so we can reuse it later.
      this.recycle();
    }

  },

  // uses Wick's 'mousedown' event
  // TIP: mousedown/mouseup work better than mouseclick if you are trying to click moving objects
  mousedown: function() {
    // remove the clip if we click it.
    this.recycle();
  }

}

So now we have an object with scripts that can control a clip. The next step is to create an interactive clip. For simplicity sake, just make a circle. On your clip's Default script, put the following code:


if (!library.add('flying_thing',this)) {
    library.applyMethods(this,FlyingThing,onEvent);
}

There's a few things going on here. The first line calls library.add, and passes a string (flying_thing) and a reference to the clip itself. This tells the library we will be referring to this clip as 'flying_thing' when we want to create instances of it.


When the library script creates clones of our clip, this script would run again, which is why it is wrapped in an if statement. Once a clip name is set in the library, calling library.add will return false. When this runs on our master clip, it will actually remove the clip from the stage and keep it in memory. You don't have to worry about hiding it, or keeping in on a super long frame like the example in my last post.


The next line of code only gets executed the first time a clip is added to the library. This line tells the library to apply the methods in our FlyingThing object to the new copy. We pass a reference to the clip, the behavior object, and the onEvent method. That part is important, because the library needs to be able to run onEvent to set any baked-in wick event handlers.


So now we have a 'flying_thing' asset in our library. Make a new frame, and put the following code on the Default script:


onEvent('keypressed', function () {
  // check if spacebar was pressed
  if (key === " ") {
    // add a copy of our flying_thing to the frame
    let thing = library.addToFrame('flying_thing', this);
  }
});

Now, whenever we push the space bar, our script will call library.addToFrame(). The first parameter tells it wich asset we want, and the second tells it what frame to add to. In this case we just use 'this' for the current frame.


IMPORTANT NOTE:

When your library script is run, it creates a global variable. Any time you add an asset to the library, that reference will stay in memory, as will any recycled copies, unless you run your game from frame 1 again. Running from frame 1 will re-declare the library object, leaving the old one for garbage collection.


You don't need to test your game from frame 1 unless you have made any changes to your clip or its behavior object. If you test your game, and see old versions of things, this will be what happened.


Also, if you haven't run the game starting at frame 1 at all, but test from a later frame, the library object will be undefined and cause problems.


So, lets just go ahead and test our game from frame 1 to be safe. When your game is ready, go ahead and hit the space bar a few times. You should see a copy of your clip move across the screen each time you hit space.


Cool right?


But that's not all this library script can do. You can add a 'recycled' method to your behaviors and maybe use that to update a score. You can add 'mousehover' and 'mouseleave' events to change the view of your clip.


The other cool thing you can do is extend the clip itself and add new methods and properties. You do this by adding an 'extend' object in your behavior object.


Let's say my game was going to have multiple ways to "kill" an object. I would probably want a single function that handled the object's death. I would start by adding this to our behavior object:


  extend: {
    killMe: function() {
      // here I could play a sound, update a score, whatever...
      this.recycle();
    }
  }, 

And then I would update our existing mousedown method to use our new killMe function.


  // uses Wick's 'mousedown' event
  // TIP: mousedown/mouseup work better than mouseclick if you are trying to click moving objects
  mousedown: function() {
    // remove the clip if we click it.
    this.killsMe();
  }

Now anything else that can kill the clip could do so by calling clip.killMe().


You can see a similar demo in action if you download this .wick file and open it at https://alpha.wickeditor.com/


This demo also shows how it plays with different layers, so you can get an idea on how you'd handle foregrounds and backgrounds.


ANOTHER NOTE:

The Wick team is planning on adding some sort of library management in the future. Don't be surprised if this library becomes obsolete. Either way, it was a fun experiment, and may be useful to people using the current build for the game jam!


8

Comments

Sigh... is it worth learning it Josh?

It can be if you want a free tool to make html5 games. It's really geared for newcomers or people who have only ever used Flash. It's perfect for simple point and click games, and stuff like that.

If you already know JavaScript and have worked in similar engines there's not really a big learning curve. If you want to jump into the game jam, it may be worth learning, but its not going to replace the tools a career game dev currently uses.

@BoMToons @PsychoGoldfish Ok, I'll play around sometime. Would be nice not to have to roll a huge stack of software and still be able to scratch the itch of making a quick web game.