11-hour video of this lecture
the lost actionscript 3.0 weekend
with james paterson, hoss gifford
shot in california by the ocean, in hd
free course content:
welcome to the lost actionscript weekend
thanks o'reilly, adobe, and fitc
notes online at http://moock.org/lectures/groundUpAS3
final code at http://moock.org/lectures/groundUpAS3/examples/virtualzoo/
flash platform overview
programming tools
languages
frameworks
runtimes
Flash platform programming tools
a text editor (Notepad, TextEdit, vi) + standalone compiler (mxmlc)
Flash authoring tool
Flex Builder
FDT
Flash Develop
Flash platform languages
ActionScript
MXML
Flash platform frameworks
Flex framework
Flash authoring components
Industry frameworks
Flash platform runtime environments
ActionScript programs run inside applications called "runtime environments"
ActionScript virtual machine (AVM)
what is an ActionScript program?
code is "compiled" into a .swf file
our focus: pure object-oriented program
lessons also apply to timeline scripting
if you're just starting out...
find a mentor
learning to program is a life-long process
if it does what you want, it's "right"
experiment with basics in Flash timeline scripts
building an airplane
think of writing a program like building an airplane
first step: blueprints
one blueprint per part (wheels, wings, seats...)
each blueprint describes a physical thing conceptually
one to one
some blueprints correspond to one thing (e.g., windshield)
one to many
some blueprints correspond to many things (e.g., seats)
master blueprint
master blueprint describes how parts fit together
interoperation
interoperation of the assembled parts produces the airplane's behavior
connected parts depend on and "know about" each other
classes and objects
airplane in flight
program running
objects
objects represent the "things" in a program
classes
class describes the characteristics and behavior of a type of object
custom vs built-in classes
some classes are written from scratch ("custom classes")
some classes are provided by ActionScript and Flash runtimes
to create an object-oriented program...
what the objects do determines the behavior of the program
main class
every program must have a main class, like a master blueprint
main class provides program starting point
to start program
the virtual zoo
a simulated zoo game with virtual pets
player must feed pets, like a Tamagotchi
why a game?
a "pet" is a well known system; focus on code-based representation
game dev work is complex, leading-edge, challenging
games include all aspects of application development:
perhaps most important: pets die
program folder
store program files in /virtualzoo/
store ActionScript source files in /virtualzoo/src/
the main class: VirtualZoo
program main class is VirtualZoo
place class code in a text file named VirtualZoo.as
filename must match class name
packages
potential problem:
to prevent name conflicts, use packages
package lengthens a class's name
person's full name: first name + last name
class's full name: package name + class name
package names
each package has a lowercase name:
game
physics
networking
packages can be nested:
game.vehicles
physics.2d
packages usually start with reverse domain:
com.yourdomain.game
org.moock.utils
classes in packages
a class in a package adopts the package name
e.g., class Player
in package game
becomes:
game.Player
a dot (.
) separates the classname from the package name
creating a package
create packages using package definition directive
directive means program instruction
a definition creates something, such as a class or a package
"define" means "create" (so does "declare")
package definition directive
here's a package definition directive:
package packageName { }
definitions start with a keyword (package
)
followed by the name of the thing being defined
the {
and }
mark the start and end of the package contents
code between {
and }
is the "package body" or "package block"
the zoo package
create a package, zoo, to hold our app's classes
package zoo { }
move VirtualZoo.as to a new folder, /zoo/
place source files in a folder structure matching package name
e.g., org/moock/zoo/VirtualZoo.as
defining a class
create classes using class definition directive:
class NameOfClass { }
definition starts with class
keyword
convention: class names are capitalized
the {
and }
mark the start and end of the class
code between {
and }
is the "class body" or "class block"
defining VirtualZoo
package zoo { class VirtualZoo { } }
the VirtualPet class
pets in the zoo will be represented by instances of the VirtualPet
class
new source file: VirtualPet.as
package zoo { class VirtualPet { } }
save in /virtualzoo/src/zoo/
zoo
package spans multiple files
convention: one class per file
the public attribute
by default, a class in a package can only be used in that package
to make class available outside package, use public
attribute
package zoo { public class VirtualZoo { } }
program's main class must be public
the internal attribute
to indicate that a class should be used within its package only, use internal
package zoo { internal class VirtualPet { } }
same as:
package zoo { class VirtualPet { } }
public
and internal
are known as access control modifiers
constructor method
constructor method initializes instances of a class
create constructor method with a function definition
package zoo { public class VirtualZoo { function VirtualZoo () { } } }
start with keyword function
then class's name
then parameters (none in this case)
then "constructor body": {
and }
constructors must be public
in ActionScript 3.0, all constructors must be public
package zoo { public class VirtualZoo { public function VirtualZoo () { } } }
initializing instances
constructor's instructions carried out when an object is created
main class constructor plays special role:
making VirtualPet objects
to put pets in the zoo, we create VirtualPet objects
general approach:
new ClassName
to make a VirtualPet object:
new VirtualPet
to make two VirtualPet objects:
new VirtualPet new VirtualPet
making objects with literals
some built-in classes have special object-creation syntax
to create a Number object:
25.4
to create a String object (representing text):
"hello"
to create a Boolean object representing the logical state true:
true
to create a Boolean object representing the logical state false:
false
putting pets in the program
add a VirtualPet object to the zoo program:
package zoo { public class VirtualZoo { public function VirtualZoo () { new VirtualPet } } }
VirtualPet referred to by class name, without package name
classes outside the zoo
package must be imported before use
package zoo { import flash.media.Sound public class VirtualZoo { public function VirtualZoo () { new Sound } } }
the lost pet
problem: program currently cannot refer to the VirtualPet object
so program cannot control VirtualPet object
use variables to refer to an object after creating it
variables and values
every object is a piece of data called a value
a variable is an identifier (i.e., a name) that refers to a value
use variables to keep track of objects
remember: variables are not containers! (details later)
local variables
four kinds of variables:
use local variables to track information within methods and functions
creating a local variable
create variables using variable definition
var identifier = value;
the "= value
" is the variable initializer
specifying value is known as "assigning" or "setting"
avoid "container" metaphor ("put value in" is wrong)
variables do not store values
the pet variable
local variable for accessing the VirtualPet object
note: not using datatypes yet (dynamic language; need to understand inheritance before using datatypes)
package zoo { public class VirtualZoo { public function VirtualZoo () { var pet = new VirtualPet; } } }
can now control the VirtualPet object through pet
but the pet can't do anything yet...needs characteristics and behaviors
tracking object characteristics
recall that a class describes:
a characteristic is a value describing something about the object
to keep track of characteristics, use instance variables
instance variables
instance variable: a variable attached to an object
width
refers to the Number 150address
refers to the String "55 Main St"create instance variables with a class-level variable definition
class SomeClass { var identifier = value; }
pet nicknames
use an instance variable to track each pet's name
package zoo { internal class VirtualPet { var petName = "Unnamed Pet"; } }
every pet is initially named "Unnamed Pet"
assigning a new name
assign new name by changing petName
's value
object.instanceVariable = value
set petName
's value to "Stan"
package zoo { public class VirtualZoo { public function VirtualZoo () { var pet = new VirtualPet; pet.petName = "Stan"; } } }
variable access-control modifiers
public
: access allowed everywhere
internal
: access allowed within variable's package only
protected
: access allowed within variable's class and descendant classes only
private
: access allowed within variable's class only
default is internal
variable access example
encapsulation
an object's variables are its own business
should be defined private to prevent external modification
characteristics should be altered via methods (discussed later)
for now, we'll make petName
internal, later private
package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; } }
initializing an object
so far, we initialize VirtualPet
object characteristics like this:
var pet = new VirtualPet; pet.petName = "Stan";
for improved convenience, use constructor parameters
constructor parameters
constructor parameter: local variable defined in constructor header
class SomeClass { function SomeClass (identifier = value) { } }
define multiple constructor parameters
class SomeClass { function SomeClass (identifier1, identifier2, identifier3) { } }
setting constructor parameter values
set parameter value with constructor argument:
new SomeClass(someValue)
set default value with an initializer:
class SomeClass { function SomeClass (identifier = value) { } }
if no initializer, argument is required
a VirtualPet constructor parameter
required parameter, name
:
package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; public function VirtualPet (name) { } } }
name
's value assigned via constructor argument:
package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet("Stan"); } } }
assigning one variable another's value
within VirtualPet constructor, name
's value is "Stan"
we want to assign that value to petName
object.instanceVariable = value
this.petName = name
this
is the VirtualPet object being created
package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; public function VirtualPet (name) { this.petName = name; } } }
remove petName's initializer
petName
will always receive a value via VirtualPet
constructor
remove redundant petName
initializer
package zoo { internal class VirtualPet { internal var petName; public function VirtualPet (name) { this.petName = name; } } }
copies and references
the following assignment can have two results:
this.petName = name;
if name
's value is a String, Boolean, Number, int, or uint
petName
if name
's value is any other object
multiple references to the same object
when two variables refer to the same object
var a = new VirtualPet("Stan"); // a.petName yields "Stan" var b = a; b.petName = "Tom" // a.petName now yields "Tom"
variables don't "contain" objects, they merely refer to them
instance variable for our pet
need a reference to the pet after VirtualZoo
constructor finishes
package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { this.pet = new VirtualPet("Stan"); } } }
behaviors for our pet
instance methods define an object's behavior
instance method is:
examples:
Sound
class's play()
methodTextField
class's setSelection()
methodwe want to give VirtualPet
an eat()
method
creating an instance method
use a function definition within the class:
class SomeClass { function identifier () { } }
code between {
and }
is the "method body"
running an instance method
execute the code in an instance method:
object.methodName()
exactly like running a function
can't eat without a stomach
new instance variable: currentCalories
set default value with initializer (= 1000)
package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } } }
the eat() method
instance method eat()
will increase currentCalories
eventually, user input will trigger eat()
eat()
skeleton code:
package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } function eat () { // Empty for now... } } }
invoking eat()
object.methodName()
package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { this.pet = new VirtualPet("Stan"); // Invoke eat() this.pet.eat(); } } }
increasing the pet's calories
add 100 to currentCalories
this.currentCalories = this.currentCalories + 100;
this
is the object doing the action (e.g., eating)
more convenient approach:
this.currentCalories += 100;
updated VirtualPet code
package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } function eat () { this.currentCalories += 100; } } }
instance-method access-control modifiers
same as for instance variables
public
: access allowed everywhere
internal
: access allowed within method's package only
protected
: access allowed within method's class and descendant classes only
private
: access allowed within method's class only
default is internal
black box principle
think of an object as a "black box"
box is controlled by external knobs
box's internal operations are unknown to person using knobs
public methods are the knobs
non-public methods are internal operations
car as a black box example
driver doesn't need to know how engine works
just press gas pedal
can change engine without affecting use of car
oops! "gas" should be "acceleration"
can change private methods without affecting public methods
API
public methods and variables are the class's API
Application Programming Interface
"Interface" used to control the instances of the class
as much as possible, API should remain fixed
changes to API force changes to code that uses the class
add eat() to VirtualPet's API
package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } public function eat () { this.currentCalories += 100; } } }
configurable meal size
parameter for "how much to eat"
package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } } }
eat 50 calories
package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { this.pet = new VirtualPet("Stan"); this.pet.eat(50); } } }
omitting this
'this
' takes time to write and adds clutter
public function eat (numberOfCalories) { currentCalories += numberOfCalories; }
when ActionScript sees an identifier, it searches:
numberOfCalories
: ActionScript finds the parameter
currentCalories
: ActionScript finds the instance variable
known as identifier resolution (see chapter 16 of Essential ActionScript 3.0)
note: small runtime performance cost for omitting this
disambiguating parameter/variable name conficts
in the code below:
calories
means parameterthis.calories
means instance variablepackage zoo { internal class VirtualPet { private var calories = 1000; public function eat (calories) { this.calories += calories; } } }
modifying state directly is bad
pet lives too long:
somePet.currentCalories = 1000000;
possible program malfunction:
somePet.currentCalories = -46;
moderate state changes through methods
restrict currentCalories
value to a maximum of 2,000
public function setCalories (newCurrentCalories) { if (newCurrentCalories > 2000) { currentCalories = 2000; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } }
update eat()
:
public function eat (numberOfCalories) { setCalories(currentCalories + numberOfCalories); }
known as modifier method (also, mutator or setter)
moderate state retrieval through methods
retrieve number of calories:
public function getCalories () { return currentCalories; }
retrieve "state of hunger":
public function getHunger () { return currentCalories / 2000; }
known as retriever method (also, accessor or getter)
methods for name modification and retrieval
limit length of assigned pet name
public function setName (newName) { // If the proposed new name has more than 20 characters... if (newName.length > 20) { newName = newName.substr(0, 20); } else if (newName == "") { return; } // new name validated, so assign it petName = newName; }
retrieve name:
public function getName () { return petName; }
restrict access to petName:
private var petName;
getName() and setName() usage
package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { pet = new VirtualPet("Stan"); // Assign the pet's old name to the local variable oldName var oldName = pet.getName(); // Give the pet a new name pet.setName("Marcos"); } } }
package zoo { internal class VirtualPet { private var petName; private var currentCalories = 1000; public function VirtualPet (name) { setName(name); } // ...remainder of class not shown } }
static variables
variables that relate to a class (not an instance)
track information relating to entire class
class SomeClass { private static var identifier = value; }
accessing static variables
Class.variable
when Class
is omitted, current class is used
EBTI: Explicit is Better Than Implicit
magic values
static variables are great for cleaning up "magic values"...
unexplained literal values
20 in setName() and 2000 in setCalories(), getHunger()
static variables for VirtualPet
pet name's maximum length
pet "stomach size" (maximum calories)
package zoo { internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; // Remainder of class not shown... } }
replace magic values with static variables
centralize code with static variables:
package zoo { internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; private var petName; // Give each pet 50% of the maximum possible calories to start with. private var currentCalories = VirtualPet.maxCalories/2; public function VirtualPet (name) { setName(name); } public function eat (numberOfCalories) { setCalories(currentCalories + numberOfCalories); } public function setCalories (newCurrentCalories) { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } } public function getCalories () { return currentCalories; } public function getHunger () { return currentCalories / VirtualPet.maxCalories; } public function setName (newName) { if (newName.length > VirtualPet.maxNameLength) { newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { return; } petName = newName; } public function getName () { return petName; } } }
static methods
methods that relate to a class (not an instance)
e.g., convert data to a given class:
Point.polar()
static-method-only classes
Some classes exist solely to define static methods
Group related functionality
Mouse.show()
, Mouse.hide()
a static-method alternative: functions
standalone set of instructions (independent of any class or object)
structurally created like methods
function identifier (param1, param2, param3) { }
package-level functions
function defined in a named package
package packageName { internal_or_public function identifier () { } }
must be internal or public
e.g., flash.utils.setTimeout()
if public, must reside in file with matching name
global functions (function in unnamed package)
function defined in the unnamed package
package { public function identifier () { } }
accessible anywhere in a program
e.g., trace()
consuming food
new VirtualPet
method to reduce calories: digest()
amount to digest governed by static variable: caloriesPerSecond
private static var caloriesPerSecond = 100;
private function digest () { trace(getName() + " digested some food."); setCalories(getCalories() - VirtualPet.caloriesPerSecond); }
triggering digest()
updated VirtualPet
constructor:
public function VirtualPet (name) { setName(name); // Call digest() once per second digestIntervalID = setInterval(digest, 1000); }
digestIntervalID
refers to a number used to cancel the interval
private var digestIntervalID;
pet death
when calories run out, stop digesting:
public function setCalories (newCurrentCalories) { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } // Calculate percentage of calories left var caloriePercentage = Math.floor(getHunger()*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); if (caloriePercentage == 0) { clearInterval(digestIntervalID); trace(getName() + " has died."); } }
dead pets don't eat
prevent eat()
from running when pet has died
public function eat (numberOfCalories) { // If this pet is dead... if (currentCalories == 0) { // ...quit this method without modifying currentCalories trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate some food."); setCalories(currentCalories + numberOfCalories); }
inheritance
relationship between 2+ classes where one borrows (or inherits) the variable and method definitions of another
lets one class use the code in another class
provides conceptual tool for representing hierarchies
follow program needs, not real world
Person
> Male
, Female
> MaleStudent
, FemaleStudent
Student
class with gender
variableinheritance example
types of food
pets can eat sushi and apples
new classes: Sushi
and Apple
Sushi
and Apple
class functionality is nearly identical
put shared functionality in a superclass, Food
Sushi
and Apple
will extend Food
the Food class
package zoo { public class Food { private var calories; private var name; public function Food (initialCalories) { setCalories(initialCalories); } public function getCalories ( ) { return calories; } public function setCalories (newCalories) { calories = newCalories; } public function getName ( ) { return name; } public function setName (newName) { name = newName; } } }
the Apple class
Apple
inherits from Food
Apple
class sets food name and default calories
package zoo { public class Apple extends Food { // Set the default number of calories for an Apple object to 100 private static var DEFAULT_CALORIES = 100; public function Apple (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } // Run Food constructor. // (Should come before assigning instance vars so superclass // doesn't overwrite assigned values.) super(initialCalories); setName("Apple"); } } }
the Sushi class
Sushi
inherits from Food
Sushi
class sets food name and default calories
package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES = 500; public function Sushi (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } // Run Food constructor super(initialCalories); setName("Sushi"); } } }
updated eat() method
revise eat() to accept Food objects
public function eat (foodItem) { if (currentCalories == 0) { trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate the " + foodItem.getName() + "."); setCalories(currentCalories + foodItem.getCalories()); }
feeding time!
try the new food out...
package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { pet = new VirtualPet("Stan"); pet.eat(new Apple()); // Feed Stan an apple pet.eat(new Sushi()); // Feed Stan some sushi } } }
wormy apples
add custom functionality for one type of food
randomly give 50% of all apples a worm
package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES = 100; private var wormInApple; public function Apple (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); wormInApple = Math.random() >= .5; setName("Apple"); } public function hasWorm () { return wormInApple; } } }
pets don't eat worms
update eat()
method to reject wormy apples
public function eat (foodItem) { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } if (foodItem is Apple) { if (foodItem.hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } } trace(getName() + " ate the " + foodItem.getName() + "."); setCalories(currentCalories + foodItem.getCalories()); }
preparing for screen display
application main class must extend Sprite
or MovieClip
allows main class instance to be added to display list
more display information coming later
for now, make VirtualZoo extend Sprite so we can compile
package zoo { import flash.display.Sprite; public class VirtualZoo extends Sprite { private var pet; public function VirtualZoo () { pet = new VirtualPet("Stan"); pet.eat(new Apple()); pet.eat(new Sushi()); } } }
compiling in Flash Authoring
select File > New
select Flash File (ActionScript 3.0), then OK
select File > Save As
save in /virtualzoo/src folder
for File name, enter VirtualZoo.fla, then OK
zoo.VirtualZoo
zoo.VirtualZoo
select Control > Test Movie
Flex Builder: "main class in package" issues
in Flex Builder 3, can't leave VirtualZoo in package "zoo"
therefore, move VirtualZoo class from zoo
package to the unnamed package...
Flash Builder: code preparatation
move VirtualZoo.as
from /virtualzoo/src/zoo
to /virtualzoo/src
in VirtualZoo.as
:
import zoo.*;
package zoo {
to package {
in VirtualPet.as
, change internal class
to public class
Flash Builder: create a project
select File > New > ActionScript Project
for Project name, enter "virtualzoo"
under Project Contents, uncheck "Use default location"
under Project Contents > Folder, select virtualzoo folder
click Next
for Main source folder, enter "src"
for Main application file, enter VirtualZoo.as
click Finish
Flash Builder: configure project
build project in standalone player instead of browser:
prevent VirtualZoo.fla from being copied to /bin-debug/ folder:
refresh build:
Flash Builder: compile and run
Flash Builder constantly compiles in background (optional)
to run program:
reference errors
accessing a nonexistent method or variable causes a reference error
pet.eatt(new Sushi()) // typo
pet.jump() // no such feature
ReferenceError: Error #1069: Property eatt not found on zoo.VirtualPet and there is no default value. at VirtualZoo$iinit()[C:\data\virtualzoo\src\VirtualZoo.as:8]
type annotations
reference errors happen at runtime
to detect reference errors at compile time, use type annotations
type annotation: tells the compiler the type of value referenced by a variable, parameter, or method return
var identifier:type = value;
function identifier (param:type):type { }
type
is any "datatype"...
datatypes
datatype: set of values
null is a datatype (set is null)
void is a datatype (set is undefined)
every class is a datatype
Food datatype
Apple datatype
type annotation example
var meal:Food = new Food();
var meal:Food = new Apple();
strict-mode type mismatch compiler error:
var meal:Food = new VirtualPet("Hoss");
Implicit coercion of a value of type zoo:VirtualPet to an unrelated type zoo:Food. VirtualZoo.as /virtualzoo/src line 13
detecting reference errors at compile-time
var pet:VirtualPet = new VirtualPet("Stan");
pet.eatt(new Sushi());
compiler error:
1061: Call to a possibly undefined method eatt through a reference with static type zoo:VirtualPet.
virtual zoo program with datatypes
package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo () { pet = new VirtualPet("Stan"); pet.eat(new Apple()); pet.eat(new Sushi()); } } }
package zoo { import flash.utils.setInterval; import flash.utils.clearInterval; public class VirtualPet { private static var maxNameLength:int = 20; private static var maxCalories:int = 2000; private static var caloriesPerSecond:int = 100; private var petName:String; private var currentCalories:int = VirtualPet.maxCalories/2; private var digestIntervalID:int; public function VirtualPet (name:String):void { setName(name); digestIntervalID = setInterval(digest, 1000); } public function eat (foodItem:Food):void { if (currentCalories == 0) { trace(getName() + " is dead. You can't feed it."); return; } if (foodItem is Apple) { if (foodItem.hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } } trace(getName() + " ate the " + foodItem.getName() + "."); setCalories(currentCalories + foodItem.getCalories()); } public function setCalories (newCurrentCalories:int):void { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } // Calculate percentage of calories left var caloriePercentage:int = Math.floor(getHunger()*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); if (caloriePercentage == 0) { clearInterval(digestIntervalID); trace(getName() + " has died."); } } public function getHunger ():Number { return currentCalories / VirtualPet.maxCalories; } public function getCalories ():int { return currentCalories; } public function setName (newName:String):void { if (newName.length > VirtualPet.maxNameLength) { newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { return; } // Assign the new, validated name to petName petName = newName; } public function getName ():String { return petName; } private function digest ():void { trace(getName() + " digested some food."); setCalories(getCalories() - VirtualPet.caloriesPerSecond); } } }
package zoo { public class Food { private var calories:int; private var name:String; public function Food (initialCalories:int) { setCalories(initialCalories); } public function getCalories ():int { return calories; } public function setCalories (newCalories:int):void { calories = newCalories; } public function getName ():String { return name; } public function setName (newName:String):void { name = newName; } } }
package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES:int = 100; private var wormInApple:Boolean; public function Apple (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); wormInApple = Math.random() >= .5; setName("Apple"); } public function hasWorm ():Boolean { return wormInApple; } } }
package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES:int = 500; public function Sushi (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); setName("Sushi"); } } }
casting
consider this code (from VirtualPet.eat()
):
foodItem.hasWorm();
to strict-mode compiler, foodItem
is a Food
instance
foodItem
might be Apple or SushiFood
does not define hasWorm()
, so errorsolution? first, check if foodItem
is an Apple object:
if (foodItem is Apple) { ... }
then use cast to tell compiler the object's actual class:
if (foodItem is Apple) { if (Apple(foodItem).hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } }
managing display
could add display code to VirtualPet
better to separate logic from display, as in "model/view/controller"...
model/view/controller
model: logic, state (data)
view: render model
controller: receive input, update model
MVC in the virtual zoo app:
real-world mvc use case
chat room interface: Room (model) and ChatRoomView (view)
user changes rooms, underlying Room object (model) changes
ChatRoomView is assigned the new room, updates automatically
separation benefits:
implementing mvc in the zoo
modify VirtualPet
to support four states: full, hungry, starving, dead
use events to tell VirtualPetView
about state changes
implementing states in VirtualPet
static variables to represent possible states
instance variable to track current pet state
modifier/retriever methods for pet state
update pet state when calories change
state variables
public static const PETSTATE_FULL:int = 0; public static const PETSTATE_HUNGRY:int = 1; public static const PETSTATE_STARVING:int = 2; public static const PETSTATE_DEAD:int = 3;
private var petState:int;
state modifier and retriever methods
private function setPetState (newState:int):void { if (newState == petState) { return; } petState = newState; }
public function getPetState ():int { return petState; }
set state when calories change
update setCalories()
private function setCalories (newCurrentCalories:int):void { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } var caloriePercentage:int = Math.floor(getHunger()*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); // Set pet state if (caloriePercentage == 0) { // The pet has no food left. Set state to dead. setPetState(VirtualPet.PETSTATE_DEAD); clearInterval(digestIntervalID); } else if (caloriePercentage < 20) { // The pet needs food badly. Set state to starving. setPetState(VirtualPet.PETSTATE_STARVING); } else if (caloriePercentage < 50) { // The pet needs food. Set state to hungry. setPetState(VirtualPet.PETSTATE_HUNGRY); } else { // The pet doesn't need food. Set state to full. setPetState(VirtualPet.PETSTATE_FULL); } }
code cleanup: centralize death
setCalories()
does death, but shouldn't (not its responsibility)
externalize this code...
if (caloriePercentage == 0) { setPetState(VirtualPet.PETSTATE_DEAD); clearInterval(digestIntervalID); }
...to new method, die()
if (caloriePercentage == 0) { die(); }
add new die()
method:
private function die ():void { // Moved from setCalories() setPetState(VirtualPet.PETSTATE_DEAD); clearInterval(digestIntervalID); trace(getName() + " has died."); }
most classes have a "death" method that releases resources
code cleanup: updated eat() method
eat()
uses states to check for death
public function eat (foodItem:Food):void { // If the pet is dead, abort if (petState == VirtualPet.PETSTATE_DEAD) { trace(getName() + " is dead. You can't feed it."); return; } // If the food item is an apple, check it for worms. If it has a worm, // don't eat it. if (foodItem is Apple) { if (Apple(foodItem).hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } } // Display a debugging message indicating what the pet ate trace(getName() + " ate the " + foodItem.getName() + " (" + foodItem.getCalories() + " calories)."); setCalories(getCalories() + foodItem.getCalories()); }
code cleanup: setting initial calories
set calories in constructor so state is set
setCalories(VirtualPet.maxCalories/2);
events and event handling
system for one object to tell other objects that something happened
event: the state change
event target: the VirtualPet
object
event listeners: methods that register to be notified of the "event"
the event cycle
dispatch state change events
extend EventDispatcher:
import flash.events.*; public class VirtualPet extends EventDispatcher {
give event a name:
public static const STATE_CHANGE:String = "STATE_CHANGE";
request event dispatch from setPetState()
:
private function setPetState (newState:int):void { if (newState == petState) { return; } petState = newState; dispatchEvent(new Event(VirtualPet.STATE_CHANGE)); }
VirtualPetView class
new class to display pet on screen
package zoo { import flash.display.*; import flash.events.*; public class VirtualPetView extends Sprite { // The pet being displayed private var pet:VirtualPet; public function VirtualPetView (pet:VirtualPet) { // Retrieve a reference to the pet being displayed this.pet = pet; // Register to be notified when the pet's condition changes pet.addEventListener(VirtualPet.STATE_CHANGE, petStateChangeListener); // NOTICE that the model doesn't control the view directly. // Instead, the view responds to generic events from the model. } private function petStateChangeListener (e:Event):void { renderCurrentPetState(); } private function renderCurrentPetState ():void { var state:int = pet.getPetState(); switch (state) { case VirtualPet.PETSTATE_FULL: trace(pet.getName() + " is now full."); break; case VirtualPet.PETSTATE_HUNGRY: trace(pet.getName() + " is now hungry."); break; case VirtualPet.PETSTATE_STARVING: trace(pet.getName() + " is now starving."); break; case VirtualPet.PETSTATE_DEAD: trace(pet.getName() + " is now dead."); break; } } } }
give our pet a VirtualPetView
new instance variable in VirtualZoo
:
private var petView:VirtualPetView;
create VirtualPetView
in VirtualZoo
constructor:
petView = new VirtualPetView(pet);
run the code to test state change event
an event for name changes
event constant in VirtualPet
:
public static const NAME_CHANGE:String = "NAME_CHANGE";
dispatch event from setName()
:
public function setName (newName:String):void { if (newName.length > VirtualPet.maxNameLength) { newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { return; } // Assign the new, validated name to petName petName = newName; dispatchEvent(new Event(VirtualPet.NAME_CHANGE)); }
handle name change events
VirtualPetView
name-change listener and name renderer:
private function petNameChangeListener (e:Event):void { renderCurrentPetName(); } private function renderCurrentPetName ():void { trace("Pet name is now: " + pet.getName()); }
register for event in VirtualPetView
constructor:
pet.addEventListener(VirtualPet.NAME_CHANGE, petNameChangeListener);
initialize name in VirtualPetView
constructor:
renderCurrentPetName();
a note on weak listeners
if a program doesn't control a listener's lifespan directly, then:
moral: forget weak or strong, instead always control listener lifespan
moral: don't use weak as a default because:
graphics!
pet logic now works, time to add display
first, learn about the display api
display api
divides display functionality into three tiers
display api core classes
DisplayObject
: base display class
InteractiveObject
: adds mouse/keyboard functionality
DisplayObjectContainer
: adds containment for grouping
Sprite
: adds dragability, button-style interaction features
MovieClip
: adds timeline control
display object creation
var t:TextField = new TextField()
var s:Sprite = new Sprite()
the display list
hierarchy of all objects currently on screen
hierarchy's root is the Stage
instance
Stage
instance is a container
Stage
's first child is auto-added: .swf's main class instance
any display children added to the main class instance appear on screen
containers and children
DisplayObjectContainer
methods:
addChild()
removeChild()
addChildAt()
removeChildAt()
display objects can exist detached from the display list
object state (x, y, rotation) retained even when detached
display api example
package { import flash.display.*; import flash.text.TextField; public class GreetingApp extends Sprite { public function GreetingApp() { // Create a rectangle var rect:Shape = new Shape(); rect.graphics.lineStyle(1); rect.graphics.beginFill(0x0000FF, 1); rect.graphics.drawRect(0, 0, 75, 50); // Create a text message var greeting_txt:TextField = new TextField(); greeting_txt.text = "Hello world"; greeting_txt.x = 60; // Add assets to the display list addChild(greeting_txt); addChild(rect); } } }
displaying the pet: architecture
displaying the pet
updates to VirtualPetView
class:
COMPLETE
event when graphics are done loadinggraphics container for loaded graphics
new VirtualPetView instance variable:
private var graphicsContainer:Sprite;
new method:
private function createGraphicsContainer ():void { graphicsContainer = new Sprite(); addChild(graphicsContainer); }
update constructor:
createGraphicsContainer(); renderCurrentPetName();
loading architecture
variables for loading graphics
use Loader
objects to load graphics
import flash.net
package:
import flash.net.*;
one object per graphic
one variable per object
private var petAlive:Loader; // The pet in its alive state private var petDead:Loader; // The pet in its dead state private var foodHungry:Loader; // An icon for the hungry state private var foodStarving:Loader; // An icon for the starving state
variables to count loaded graphics
keep track of how many graphics have loaded
when all graphics have loaded, trigger COMPLETE event
static private var numGraphicsToLoad:int = 4; private var numGraphicsLoaded:int = 0;
method to manage loading
why contentLoaderInfo
?
private function loadGraphics ():void { // Graphic showing the pet in its alive state petAlive = new Loader(); petAlive.load(new URLRequest("pet-alive.gif")); petAlive.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); petAlive.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); // Graphic showing the pet in its dead state petDead = new Loader(); petDead.load(new URLRequest("pet-dead.gif")); petDead.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); petDead.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); // The "needs food" icon foodHungry = new Loader(); foodHungry.load(new URLRequest("food-hungry.gif")); foodHungry.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); foodHungry.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); foodHungry.x = 15; foodHungry.y = 100; // The "needs food badly" icon foodStarving = new Loader(); foodStarving.load(new URLRequest("food-starving.gif")); foodStarving.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); foodStarving.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); foodStarving.x = 15; foodStarving.y = 100; }
listener triggered when a graphic loads
private function completeListener (e:Event):void { // Increase (by one) the count of the total number of graphics loaded numGraphicsLoaded++; // If all the graphics have loaded... if (numGraphicsLoaded == numGraphicsToLoad) { // Render pet, then trigger COMPLETE event renderCurrentPetState(); dispatchEvent(new Event(Event.COMPLETE)); } }
listener triggered when graphic load fails
private function ioErrorListener (e:IOErrorEvent):void { trace("Load error: " + e); }
update pet renderer
private function renderCurrentPetState ():void { // Clear all graphics while (graphicsContainer.numChildren > 0) { graphicsContainer.removeChildAt(0); } // Check the pet's current state var state:int = pet.getPetState(); // Display appropriate graphics switch (state) { case VirtualPet.PETSTATE_FULL: graphicsContainer.addChild(petAlive); break; case VirtualPet.PETSTATE_HUNGRY: graphicsContainer.addChild(petAlive); graphicsContainer.addChild(foodHungry); break; case VirtualPet.PETSTATE_STARVING: graphicsContainer.addChild(petAlive); graphicsContainer.addChild(foodStarving); break; case VirtualPet.PETSTATE_DEAD: graphicsContainer.addChild(petDead); break; } }
update VirtualPetView constructor
load the graphics when a VirtualPetView
is created
createGraphicsContainer(); loadGraphics(); renderCurrentPetName();
don't start pet until graphics have loaded
new VirtualPet
methods: stop()
and start()
move clearInterval()
call from die()
to stop()
public function stop ():void { clearInterval(digestIntervalID); }
private function die ():void { // Stop digesting stop(); setPetState(VirtualPet.PETSTATE_DEAD); trace(getName() + " has died."); }
move setInterval()
call from constructor to start()
public function start ():void { stop(); digestIntervalID = setInterval(digest, 1000); }
start pet when graphics have loaded
import events
package
import flash.events.*;
register for "graphics loaded" notification in VirtualZoo
constructor:
petView.addEventListener(Event.COMPLETE, petViewCompleteListener);
when graphics load, start pet and display graphics
public function petViewCompleteListener (e:Event):void { addChild(petView); pet.start(); }
display pet's name
in VirtualPetView
, import the flash.text
package
import flash.text.*;
new variable for a TextField
object
private var petName:TextField;
create name text field
new method to create TextField
object:
private function createNameTag ():void { petName = new TextField(); petName.defaultTextFormat = new TextFormat("_sans",14,0x006666,true); petName.autoSize = TextFieldAutoSize.CENTER; petName.selectable = false; petName.x = 250; petName.y = 20; addChild(petName); }
call createNameTag()
from VirtualPetView
constructor:
createGraphicsContainer(); loadGraphics(); createNameTag(); renderCurrentPetName();
display name on screen
update renderCurrentPetName()
:
private function renderCurrentPetName ():void { petName.text = pet.getName(); }
interactivity
buttons for feeding the pet
new class: FoodButton
package zoo { import flash.display.* import flash.events.*; import flash.text.*; // The FoodButton class represents a simple clickable-text button public class FoodButton extends Sprite { // The text to be clicked private var text:TextField; // The formatting of the text when it is *not* under the mouse pointer private var upFormat:TextFormat; // The formatting of the text when it *is* under the mouse pointer private var overFormat:TextFormat; // Constructor public function FoodButton (label:String) { // Enable the "hand" mouse pointer for direct interactions with this object // (The buttonMode variable is inherited from Sprite.) buttonMode = true; // Disable mouse events for this object's children. Otherwise, mouse events // would target the textfield, and the hand pointer wouldn't appear. // (The mouseChildren variable is inherited // from DisplayObjectContainer.) mouseChildren = false; // Define the text formatting used when this object is *not* // under the mouse pointer upFormat = new TextFormat("_sans",12,0x006666,true); // Define the text formatting used when this object *is* // under the mouse pointer overFormat = new TextFormat("_sans",12,0x009999,true); // Create the clickable text field, and add it to this object's // display hierarchy text = new TextField(); text.defaultTextFormat = upFormat; text.text = label; text.autoSize = TextFieldAutoSize.CENTER; text.selectable = false; addChild(text); // Register to be notified when the mouse moves over this object addEventListener(MouseEvent.MOUSE_OVER, mouseOverListener); // Register to be notified when the mouse moves off of this object addEventListener(MouseEvent.MOUSE_OUT, mouseOutListener); } // Disables mouse event notifications for this object public function disable ():void { // (The mouseEnabled variable is inherited from InteractiveObject.) mouseEnabled = false; } // Triggered when the mouse moves over this object public function mouseOverListener (e:MouseEvent):void { // Apply the "mouse over" text format text.setTextFormat(overFormat); } // Triggered when the mouse moves off of this object public function mouseOutListener (e:MouseEvent):void { // Apply the "mouse not over" text format text.setTextFormat(upFormat); } } }
food button variables
in VirtualPetView, one variable for "Feed Sushi" button, one for "Feed Apple"
private var appleBtn:FoodButton; private var sushiBtn:FoodButton;
create food buttons
method to create buttons
private function createUI ():void { // The Feed Apple button appleBtn = new FoodButton("Feed Apple"); appleBtn.y = 170; appleBtn.addEventListener(MouseEvent.CLICK, appleBtnClick); addChild(appleBtn); // The Feed Sushi button sushiBtn = new FoodButton("Feed Sushi"); sushiBtn.y = 190; sushiBtn.addEventListener(MouseEvent.CLICK, sushiBtnClick); addChild(sushiBtn); }
call createUI()
from VirtualPetView
constructor:
createGraphicsContainer(); loadGraphics(); createNameTag(); renderCurrentPetName(); createUI();
button click handlers
private function appleBtnClick (e:MouseEvent):void { // Feed the pet an apple pet.eat(new Apple()); } private function sushiBtnClick (e:MouseEvent):void { // Feed the pet some sushi pet.eat(new Sushi()); }
disable interface when pet dies
new VirtualPetView
method:
private function disableUI ():void { appleBtn.disable(); sushiBtn.disable(); }
update petStateChangeListener()
:
private function petStateChangeListener (e:Event):void { if (pet.getPetState() == VirtualPet.PETSTATE_DEAD) { disableUI(); } renderCurrentPetState(); }
was it worth it?
timeline vs oop:
oop benefits:
bonus round 1: the flex debugger
select Window > Perspective > Flex Debugging
select Run > Debug VirtualZoo
break points (app also pauses for errors)
inspecting variable values
stepping through code (check flow, branches, and values)
bonus round 2: the flex profiler
select Run > Profile VirtualZoo, then Resume
memory leaks: check "Live Objects"
method execution bottlenecks
bonus round 3: flash authoring/builder workflow
create graphical assets in flash, code in flex
bonus round 4: source control
subversion or cvs
free hosting options available for small projects
benefits:
that's all folks!
audience.thank()
things to try: