/
Автор: Gladstien K.
Теги: programming languages programming information technology computer technology
ISBN: 978-1-4354-6020-1
Год: 2014
Текст
Flash® Game
Development
in a Social,
Mobile, and
3D World
Keith Gladstien
Cengage Learning PTR
Australia • Brazil • Japan • Korea • Mexico • Singapore • Spain • United Kingdom • United States
Flash® Game Development in a
Social, Mobile, and 3D World
Keith Gladstien
Publisher and General Manager,
Cengage Learning PTR:
Stacy L. Hiquet
Associate Director of Marketing:
Sarah Panella
Manager of Editorial Services:
Heather Talbot
Senior Marketing Manager:
Mark Hughes
© 2014 Cengage Learning PTR.
ALL RIGHTS RESERVED. No part of this work covered by the
copyright herein may be reproduced, transmitted, stored, or used in
any form or by any means graphic, electronic, or mechanical,
including but not limited to photocopying, recording, scanning,
digitizing, taping, Web distribution, information networks, or
information storage and retrieval systems, except as permitted
under Section 107 or 108 of the 1976 United States Copyright Act,
without the prior written permission of the publisher.
For product information and technology assistance, contact us at
Cengage Learning Customer & Sales Support, 1-800-354-9706
For permission to use material from this text or product,
submit all requests online at cengage.com/permissions
Senior Acquisitions Editor: Emi Smith
Further permissions questions can be emailed to
permissionrequest@cengage.com
Project Editor: Dan Foster, Scribe
Tribe
Technical Reviewer: Glen Rhodes
Interior Layout Tech: MPS Limited
Adobe® Flash® is a registered trademark of Adobe Systems
Incorporated.
Cover Designer: Luke Fletcher
All other trademarks are the property of their respective owners.
Proofreader & Indexer: Kelly Talbot
Editing Services
All images © Cengage Learning unless otherwise noted.
Copy Editor: Cathleen Small
Library of Congress Control Number: 2013932035
ISBN-13: 978-1-4354-6020-1
ISBN-10: 1-4354-6020-0
eISBN-10:1-4354-6021-9
Cengage Learning PTR
20 Channel Center Street
Boston, MA 02210
USA
Cengage Learning is a leading provider of customized learning
solutions with office locations around the globe, including Singapore, the United Kingdom, Australia, Mexico, Brazil, and Japan.
Locate your local office at: international.cengage.com/region
Cengage Learning products are represented in Canada by Nelson
Education, Ltd.
For your lifelong learning solutions, visit cengageptr.com
Visit our corporate website at cengage.com
Printed in the United States of America
1 2 3 4 5 6 7 15 14 13
To my father, Russell S. Gladstien (1918–2012).
Acknowledgments
To Dan Foster, Emi Smith, Cathleen Small, Kelly Talbot, Glen Rhodes, Luke Fletcher,
Mark Garvey, and everyone else involved in the publication of this book: Thank you!
iv
About the Author
Keith Gladstien was educated at the University of California, Irvine (B.A.), Louisiana
State University in New Orleans (M.S.), Purdue University (Ph.D.), University of
California, Los Angeles (postdoctoral scholar), Yale University (M.D.), and a second
stint at the University of California, Los Angeles (pediatrics residency). Or, at least
many tried their best to educate him.
His interest in ActionScript programming started more than 10 years ago and has
increased as his involvement with the Adobe ActionScript and Flash forums has
expanded.
He has done and continues to do freelance work for hundreds of clients worldwide.
He is an Adobe Certified Expert in Flash CS6.
v
Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Chapter 1
Problematic Code: Debugging and Testing . . . . . . . . . . . . . . . . . . 1
Debugging Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
Permit Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2
Compile-Time Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Run-Time Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Custom Profile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
Errors That Trigger Error Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Errors That Do Not Trigger Error Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Testing and Experimenting. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Final Words on Debugging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Chapter 2
Avoiding Problematic Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Timeline Coding versus Class File Coding . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
MovieClip Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Class Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Function Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Chapter 3
Writing Class Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Internal, Private, Protected, and Public Properties . . . . . . . . . . . . . . . . . . . . . 31
Getters and Setters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Example 1: Passing a Variable from Main to C1 via the C1
Constructor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
vi
Contents
Example 2: Passing a Variable from Main to C1 Using a C1
Public Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Example 3: Using an Event Dispatcher . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
static Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Internal, Private, Protected, Public, and Static Methods . . . . . . . . . . . . . . . . . 42
Example 1: public Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Example 2: static public Method. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Singleton Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
dynamic Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Chapter 4
What You Should Know. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Arrays. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Associative Arrays. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Array Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
BitmapData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Conditional Compiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Dictionary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
DispatchEvent. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
ExternalInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
getTimer() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Listeners versus Weak Listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
do Loops, for Loops, and while Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
enterFrame, setInterval(), and Timer Loops. . . . . . . . . . . . . . . . . . . . . . . . 60
Math Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Geometry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Linear Interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Modulo Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Randomizing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Trigonometry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
MouseOut versus RollOut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Pausing/Restarting Your Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Registration Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
SharedObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Tweening with ActionScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Chapter 5
Using the Flash API and Starting a Flash Game . . . . . . . . . . . . . 81
Step 1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Version 0a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
vii
viii
Contents
Step 2. . . . .
Version 0b .
Version 0c. .
Version 0d .
Version _01.
Chapter 6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 88
. 90
. 91
. 93
. 98
Developing a Flash Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Version
Version
Version
Version
Version
Version
Version
Version
Chapter 7
.
.
.
.
.
_02.
_03.
_04.
_05.
_06.
_07.
_08.
_09.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 108
. 110
. 115
. 119
. 126
. 135
. 149
. 173
Optimizing Game Performance. . . . . . . . . . . . . . . . . . . . . . . . . 193
Judging and Measuring Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
CPU/GPU Usage and Memory Consumption . . . . . . . . . . . . . . . . . . . . . . . . . 195
Memory Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Memory Tracking, Memory Use, and Performance Testing . . . . . . . . . . . . . . 199
Optimization Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Easiest to Hardest to Implement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Greatest to Least Benefit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Memory Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Managing CPU/GPU Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Arithmetic Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Bitmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Mouse Interactivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Remove Event Listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Stage3D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Type All Variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Use Vectors Instead of Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Chapter 8
Developing and Distributing Games for iOS Devices . . . . . . . . 259
The Tank Combat Game for the iPad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Testing an iOS Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
Publishing Your Game for iOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Air for iOS Settings: General Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
Air for iOS Settings: Deployment Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
Contents
Air for iOS Settings: Icons Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
Air for iOS Settings: Languages Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
Distributing Your Game for iOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
Chapter 9
Developing and Distributing Games for Android Devices. . . . . 337
Switcher for Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Testing an Android Game. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
AIR Debug Launcher. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
Android Emulators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
Android Debug Bridge (ADB) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Adobe AIR Developer Tool (ADT) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
Publishing Your Game for Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
AIR for Android Settings: General Tab . . . . . . . . . . . . . . . . . . . . . . . . . . 374
AIR for Android Settings: Deployment Tab. . . . . . . . . . . . . . . . . . . . . . . 377
AIR for Android Settings: Icons Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
AIR for Android Settings: Permissions Tab . . . . . . . . . . . . . . . . . . . . . . . 380
AIR for Android Settings: Languages Tab . . . . . . . . . . . . . . . . . . . . . . . . 381
Distributing Your Game for Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
Chapter 10
3D Game Development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Flare3D. . .
Version 01
Version 02
Version 03
Version 04
Version 05
Chapter 11
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 384
. 387
. 393
. 401
. 411
. 421
Social Gaming: Social Networks . . . . . . . . . . . . . . . . . . . . . . . . 437
Facebook . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
Facebook JavaScript API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Adobe’s Facebook ActionScript API . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
Twitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
Adding Twitter Buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484
Requests That Do Not Require Authentication . . . . . . . . . . . . . . . . . . . . 486
Requests That Require Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 492
Google+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508
Adding a +1 Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Adding a Badge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Adding a Share Button. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
Google+ Plug-In Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
Google+ API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
ix
x
Contents
Chapter 12
Social Gaming: Multiplayer Games . . . . . . . . . . . . . . . . . . . . . . 553
Server-Based Multiplayer Games. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
Peer-to-Peer Games . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
Appendix A Errors That Trigger an Error Message . . . . . . . . . . . . . . . . . . . . 575
Appendix B
Errors That Do Not Trigger Error Messages . . . . . . . . . . . . . . . 587
Code That Doesn’t Work . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Errors Caused by Asynchronous Code Execution . . . . . . . . . . .
gotoAndPlay and gotoAndStop (to a Frame Not Yet Loaded)
Lost Object References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Repeatedly Executed Code . . . . . . . . . . . . . . . . . . . . . . . . . . .
Problems Related to Class File Use . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 587
. 590
. 591
. 592
. 594
. 595
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
Introduction
I have been helping solve ActionScript problems for more than 10 years on the
Adobe ActionScript forums. During that time, I have addressed tens of thousands
of ActionScript issues encountered by forum posters while coding their applications
and games.
So, this book was written from that perspective and covers everything needed to solve
the problems and errors I’ve addressed on the Adobe forums.
And, while I surely have not addressed every problem and error that can occur, based
on the observation that I rarely see a new problem or error on the forums, I think it’s
safe to say that this book covers almost every problem and error that can occur when
using ActionScript.
What You’ll Find in This Book
This book is ostensibly about using Flash Pro and ActionScript 3.0 to create games.
And, while this book is focused on Flash game development, most of it also applies to
non-game Flash applications. So, this book includes everything needed to solve every
ActionScript problem and error you’re likely to encounter.
Much of that problem-solving information is in the first 16 or so pages of this book:
Chapter 1. I believe it is the most important chapter in this book and I recommend
that you do not bypass it.
xi
xii
Introduction
Who This Book Is For
This book is intended for developers who already know the basics of ActionScript 3.0
and are at least intermediate-level coders.
For readers who need an introduction to ActionScript 3.0, I recommend Trevor
McCauley’s tutorial:
www.senocular.com/flash/tutorials/as3withflashcs3/
Readers new to ActionScript 3.0 may also find the following links helpful:
www.adobe.com/devnet/actionscript/learning/as3-fundamentals/syntax.html
www.adobe.com/devnet/actionscript/learning.html
www.adobe.com/devnet/actionscript/learning/oop-concepts/writing-classes.html
How This Book Is Organized
Following is a chapter-by-chapter synopsis of what is covered in this book.
n
Chapter 1 covers how to debug and test ActionScript coding.
n
Chapter 2 discusses scope, timeline vs. class coding, and why class coding prevents a major problem introduced by timeline coding.
n
Chapter 3 shows how to write class code.
n
Chapter 4 covers topics that ActionScript programmers need to know before
starting to program games.
n
Chapter 5 covers how to find information about everything not covered in the
previous chapter.
n
Chapter 6 shows one way to start developing a game and how to solve problems
that typically occur while developing a game using the information from Chapters 4 and 5.
n
Chapter 7 discusses optimizing game performance.
n
Chapter 8 covers, in detail, how to develop and distribute games for iOS devices.
n
Chapter 9 covers, in detail, how to develop and distribute games for Android devices.
n
Chapter 10 shows one way to start 3D game development.
n
Chapter 11 covers social gaming using the social networks Twitter, Facebook,
and Google+.
n
Chapter 12 shows how to develop multiplayer games using peer-to-peer
communication.
Introduction
Companion Website Downloads
This book has a companion website offering content related to this book. You can
download files from www.cengageptr.com/downloads. Simply enter this book’s
title, ISBN, or author’s name in the Companion Search field at the top and click on
the Search button. You’ll be taken to the book’s companion page, where you can
download the related files.
The companion page for this book contains almost all the code that appears in this
book. That code is distributed among a number of files you should download from
the companion page. The file directories and names that correspond to the code
within this book are mentioned in the book’s text.
Because this book’s page width constrains the length of text lines, one line of code in
this book may be broken into two or more lines of printed text, which can make
that line of code difficult to read. However, it is likely that the width of your
preferred ActionScript editor allows most lines of code to be displayed without line
breaks, which makes the code in the downloaded files much easier to read than the
code printed in this book.
xiii
This page intentionally left blank
Chapter 1
Problematic Code:
Debugging and Testing
I don’t know whether anyone can write errorless code on the first attempt, but I
know I usually don’t. Based on my experience, I believe you’ll certainly need to
debug errors in your code as soon as you start writing it. (In other words, I make
many errors, and I assume everyone else does, too.)
Learning how to debug your code is crucial to all programming—not just for Flash,
not just for ActionScript 3.0, and not just for complex Flash applications such as
games. This chapter will show you how to set up Flash to facilitate debugging, how
to deal with Flash-triggered error messages, and how to debug errors that don’t trigger error messages.
Debugging Tools
Efficient code debugging requires two things: deductive reasoning (which I won’t
explicitly address) and tools (which I will cover in detail) that let you check your
project state at any line of code. The only two tools you need to check your Flash
game’s state and debug ActionScript are the trace() function and, when you cannot
use that (for example, when you’re testing on a mobile platform), a textfield. For the
remainder of this book, if I mention using the trace() function and its output isn’t
available to you because of your test environment, use a temporary (debugging)
textfield.
1
2
Chapter 1 n Problematic Code: Debugging and Testing
You could use a debugging textfield in all situations, but using the trace() function is
easier, and it’s faster to set up. Therefore, when trace() function output is available,
most coders prefer to use it. Just remember to remove your trace() functions, check
“Omit trace statements” in the Publish Settings, or comment out your trace() functions when your game is complete and ready for final deployment. If you fail to do
that, then those of us with debug Flash Player versions will have our log files inundated with your trace() function output. Moreover, that output will impair your
game’s performance.
To debug server-based web projects, I still use the trace() function. To see trace()
function output in my browser (and much more), I use the Firefox browser (www.
mozilla.org/en-US/firefox/fx/) with the Firebug (getfirebug.com) and FlashFirebug
(www.o-minds.com/products/Flashfirebug) add-ons. trace() function output is in
the Firebug panel > Flash tab > Output tab.
You will need to use a debug version of the Flash Player (www.adobe.com/support/
flashplayer/downloads.html) for web-project debugging. If you’re doing any online
debugging, these tools are an immense help. If you deploy your project online, you
will probably be doing online debugging. If you deploy more than a few projects
online, you will be doing online debugging.
Permit Debugging
Although those are the only debugging tools needed, if you’re using one of the Flash
Pro development environments (CS3, CS4, CS5, CS5.5, CS6, and more will follow),
you can speed debugging by using the Flash error messages to pinpoint errors. To
pinpoint the exact location of errors, you should enable “Permit debugging” (File >
Publish Settings > Flash, check the “Permit debugging” check box, and then click
OK). See Figure 1.1.
Permit Debugging
Check
Permit
debugging
Click OK
Figure 1.1
To enable detailed error messages, check “Permit debugging” and then click OK.
Source: Adobe Systems Incorporated.
By checking “Permit debugging,” you will see more detailed/helpful error messages.
The exact contents of the error messages will depend on the error type (compile-time
or run-time), the exact error, and the location (timeline or class file) of your
problematic code. But in all scenarios (except for errors that occur during an asynchronous event, such as file loading), the error message will indicate the exact line
number of the problematic code—if you check “Permit debugging.”
Flash checks for all compile-time errors before checking for any run-time errors
(which makes sense). Compile-time errors present as soon as your test movie panel
3
4
Chapter 1 n Problematic Code: Debugging and Testing
opens and before any code executes. Run-time errors can occur at any time while
your project is running and display when the Flash Player tries to execute problematic code.
Compile-Time Errors
Compile-time errors will appear in your Compiler Errors panel (see Figure 1.2).
Figure 1.2
Compile-time errors appear in your Compiler Errors panel and prevent all code from executing.
Source: Adobe Systems Incorporated.
Compile-time errors prevent all code from executing, no matter where it is located.
You’ll see your main timeline play (along with any MovieClips) from start to finish
and then loop. It usually looks like a headache-inducing mess. If you’re prone to seizures, click the Close button on your test movie panel as soon as possible.
You don’t need to read the error messages before closing the test movie panel. The
error messages will remain in the Compiler Errors panel, where you can read at your
leisure so you don’t suffer a migraine or grand mal seizure.
With compile-time errors, you can double-click the error message in the Compiler
Errors panel, and Flash will display the highlighted line of problematic code in the
Actions panel, regardless of whether the problematic line of code is attached to a
timeline or in a class file. If your class file isn’t open, Flash will open it (Figure 1.3).
You can also read the exact location of the error under the Location header. If
the problematic line of code is attached to a MovieClip timeline, this will list the
offending MovieClip’s symbol name (the library name), layer name, frame number,
and line number.
Permit Debugging
Figure 1.3
Compile-time error in a class file.
Source: Adobe Systems Incorporated.
However, if your error is on the main timeline, instead of the symbol name you’ll see
the scene name or document name (if you have a document class). (Refer to Figure 1.2.)
It’s a good idea to learn how to read the error’s location, because double-clicking
doesn’t always work correctly. If the highlighted line doesn’t match the line number
listed in the error message, use the line number in the error message. It is always
correct. After you correct all compile-time errors and retest, you will see run-time
errors, if there are any.
Run-Time Errors
With run-time errors (displayed in the Output panel, not the Compiler Errors panel),
double-clicking the error message doesn’t do anything useful. You must read the
error message. (See Figure 1.4.)
Figure 1.4
Run-time error on the main timeline.
Source: Adobe Systems Incorporated.
5
6
Chapter 1 n Problematic Code: Debugging and Testing
With file loading (asynchronous) errors, the error message will have only one line. Even
if you checked “Permit debugging,” there won’t be any information to help you pinpoint
the load method that triggered the error. Fortunately, because that error message will
occur shortly after your problematic load method, it is usually easy to determine which
(if you have more than one) load method triggered the error.
If you have a number of load methods and there’s no quick and easy way to determine which load method triggered the error, you can use the trace() function.
Because run-time errors stop code execution only in the scope (defined in Chapter 2,
“Avoiding Problematic Code”) of the problematic code and stop only the code that
runs after the error, trace() function output just before the problematic line of code
will work, and trace() function output just after the problematic line of code will fail.
Using that information will always allow you to pinpoint the line of code that triggers
a load error.
For all other run-time errors, the error message will pinpoint the exact location of the
error. The error message will have at least two lines, and often there will be many
more that display the entire sequence of function calls that led to the problematic
line of code trying to execute (which is the always in the second line of the error
message). See Figure 1.5.
Figure 1.5
Run-time error on the main timeline showing the sequence of function calls that triggered the problematic
line of code.
Source: Adobe Systems Incorporated.
You’ll rarely need to check more than the first two lines in the error message. The first
line lists the error number with a brief explanation of the error, and the second line lists
the exact location of the problematic line of code.
I’ll discuss the first line of the error message in the upcoming “Errors That Trigger
Error Messages” section. The second line will look something like
at you_can_ignore_this_part[good_stuff_here].
Permit Debugging
The bracketed content has all the information you need to pinpoint the run-time
error.
If the error is on a FLA file’s main timeline, you will see in the brackets the SWF
file’s name_fla.MainTimeline::frame number:line number. For example, the error displayed in Figure 1.5 reveals a 1034 error on the main timeline of the FLA that published Untitled.swf at Frame 1, Line 17.
The following is an aside that may prove helpful when you’re testing files. Flash
doesn’t handle the dash in a SWF name correctly. That seems strange, because
Flash uses a dash in FLA names by default, and by default the SWF name will
match and have a dash.
Anyway, the SWF name displayed in run-time error messages reflects this problem with
dashes. I was testing Untitled-1.fla, which published Untitled-1.swf, but Figure 1.5
displays an error message that implies a problem with Untitled.swf. That may or may
not interest you, but it’s not a significant problem to deal with.
However, if you use something like Untitled-1.swf to load Untitled-2.swf, you will
have all sorts of problems because Flash doesn’t appear to distinguish those two
SWFs. It seems as if they both look like Untitled.swf to Flash, causing your loading
SWF to try to load itself instead of Untitled-2.swf. Until you realize that the problem
is Flash, you might waste time trying to debug errorless code.
Other than the problem with dashes that prematurely terminates the SWF’s file
name, the SWF’s full name will be in the run-time error message.
If the error is on any timeline other than the main timeline, in the brackets you will
see the SWF’s name_fla.symbol name_error number::frame number:line number. For
an example, see Figure 1.6.
Figure 1.6
A 1034 error on the timeline of Symbol1 at Frame 1, Line 19.
Source: Adobe Systems Incorporated.
7
8
Chapter 1 n Problematic Code: Debugging and Testing
Here the symbol name is the library symbol, not the instance or reference name. Look in
your Library panel to find that symbol. White space in the symbol name is removed for
display in the error message.
The _1 appended to MovieClip symbol Symbol1 means Symbol1 is the first MovieClip
symbol with a run-time error. If there are other MovieClip symbols with run-time
errors, an underscore and increasing whole numbers are appended to the symbol
name. I’ve never found that factoid particularly helpful, but I think too much information is better than too little (at least as it applies to debugging).
If the error is in a class file, in the brackets you will see the class file path/name:problematic line number. For an example, see Figure 1.7.
Figure 1.7
A 1034 error on Line 13 in the class file MC.as in the F:\Flash\_test files\ directory.
Source: Adobe Systems Incorporated.
You might think the SWF file name would be a superfluous bit of information,
too. But the culprit SWF with the run-time error may not be your main SWF. If
you load a SWF that has a run-time error, you will find that the SWF name in the
error message is helpful. (A SWF that would contain a compile-time error if Flash
were to publish the SWF and add the problematic code contains no code, so you
cannot load a SWF that has a compile-time error.)
Custom Profile
Instead of doing the same File > Publish Settings > and so on clicking and checking
each time you open a new FLA file, you can create a custom profile that checks that
option, and any other options you typically use, by default. Unfortunately, you still
have to import a custom profile each time you open a new FLA, which requires
almost as many clicks as selecting your desired options each time you open a
new FLA.
Custom Profile
To create a new default profile that will select your desired options each time you
open a FLA without requiring any clicks, do the following after checking “Permit
debugging”:
1. Click OK to close the ActionScript Settings panel.
2. Click the gear icon (Profile Options) at the upper left of the Publish Settings
panel. (Click File > Publish Settings if your Publish Settings panel is closed.)
3. Click Export Profile and give your exported profile a name other than default.
xml.
4. Save it to the default directory suggested by Flash.
5. Open a file browser and copy your saved profile from the default directory (you
can check that location by clicking the gear icon and inspecting the path, or you
can search for your saved profile) to the subdirectory en_US/First Run/Publish
Profiles in your Flash Pro’s install directory. If your install language isn’t en_US,
that first subdirectory will have a different (but easy-to-recognize) name.
6. In both the Publish Profiles subdirectory and the default subdirectory, rename
default.xml to the name you generally use to save original files (such as
default_BU.xml, default_ORIG.xml, $default.xml, or whatever convention
you use).
7. Copy your custom-named exported profile to the Publish Profiles subdirectory.
8. Rename the copied exported profile in the Publish Profiles subdirectory to
default.xml.
9. Copy default.xml from the Publish Profiles subdirectory to the default directory.
10. Copy your custom-named profile from the default directory to the Publish
Profiles subdirectory.
You should have three files in each directory. (I don’t see why there’s a need for all of
those files, but deleting any other than the backups may cause Flash to revert to its
original default settings. You may not find that happening until you restart your
computer, so be careful if you’re testing which files you can delete.)
After you set up that custom profile, you will start with “Permit debugging” checked
when you open Flash Pro. You can save other preferred settings as the default for all
of your FLA documents in the same way.
If you work on only a few projects per year, it may not be worth that 10-step effort to
create a custom profile. But even if you work on only one Flash project, it will be
worthwhile to enable “Permit debugging.”
9
10
Chapter 1 n Problematic Code: Debugging and Testing
Errors That Trigger Error Messages
You might find that some error messages need no explanation (such as Compiler
Error 1021: Duplicate Function Definition). But if you need further explanation,
check the Flash help file Appendixes (Help > Flash Help > ActionScript 3.0 and
Components > ActionScript 3.0 Reference for the Adobe Flash Platform > Appendixes), where you’ll find a complete listing of all compiler and run-time error messages, often with additional and helpful information. As of this writing, that link is
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/appendixes.html,
but that may change as Adobe publishes updated help files.
This is the first place you should check when you encounter an error message that
you don’t completely understand. The additional information may be enough to
save you hours of hair-pulling frustration.
For example, if Error 1021: Duplicate Function Definition isn’t clear enough for you
to understand the problem, the additional, “You cannot declare more than one function with the same identifier name within the same scope,” may be enough to help
you resolve the issue.
But if that’s not enough help, don’t be afraid to use a search engine. Searching for
“Flash as3 error xxxx” should bring up all sorts of information, some of which may
be helpful.
Further, this book’s Appendix A, “Errors That Trigger an Error Message” lists the
most common Flash error messages encountered by people posting on the Adobe
forums, along with advice on how to solve them. The error messages covered in
Appendix A (a small subset of the errors listed in the Flash help appendixes) are discussed in much greater detail than in the Flash help files.
Appendix A encapsulates some of my experience helping on the Adobe Flash forums
(http://forums.adobe.com/community/flash/flash_actionscript?view=discussions&start=
0&numResults=50, http://forums.adobe.com/community/flash/flash_actionscript3?view=
discussions&start=0&numResults=50, http://forums.adobe.com/community/flash/flash_
general?view=discussions&start=0), answering many thousands of queries, plus my
experience with the errors I’ve made coding Flash projects.
If you’re in no rush for an answer, there is also help available on the Adobe Flash
ActionScript 3 forum (http://forums.adobe.com/community/flash/flash_action
script3?view=discussions&start=0&numResults=50). You should use whatever combination of help resources works best for you.
Errors That Do Not Trigger Error Messages
Errors That Do Not Trigger Error Messages
Most ActionScript programming errors trigger a Flash error message—either a
compile-time error or a run-time error. However, some errors don’t trigger a Flash
message. These are the result of some combination of faulty logic and a failure to
understand how Flash/ActionScript works. I’ll discuss the more common of these
errors in Appendix B, “Errors That Do Not Trigger an Error Message.”
But whether you encounter a common error or an uncommon one, you can still
debug it using the trace() function. Generally, your code will work up to a certain
point and then fail. That failure point is where you should start using the trace()
function to debug your error.
For example, in the following code, I create two red circles, place them on-stage, and
assign MouseEvent.CLICK listeners. The problem is that the leftmost red circle doesn’t
respond to the mouse. (See Figure 1.8.)
var mc:MovieClip;
mc=new MovieClip();
with(mc.graphics){
beginFill(0xaa0000);
drawCircle(0,0,20);
endFill()
}
addChild(mc);
mc.x=100;
mc.y=100;
mc=new MovieClip(); // this is where the problem starts
with(mc.graphics){
beginFill(0xaa0000);
drawCircle(0,0,20);
endFill()
}
addChild(mc);
mc.x=200;
mc.y=100;
mc.addEventListener(MouseEvent.CLICK,f);
mc.addEventListener(MouseEvent.CLICK,f);
function f(e:MouseEvent):void{
trace(e.currentTarget.x);
}
11
12
Chapter 1 n Problematic Code: Debugging and Testing
Does not
respond to
the mouse
Does
respond
to the
mouse
Figure 1.8
The on-stage display after executing the code.
Source: Adobe Systems Incorporated.
This code works (both circles are created and added to the display in the expected
locations) up until the leftmost red circle (supposedly) has its listener added.
To check whether the leftmost circle has a MouseEvent listener added, you need to
use some code that distinguishes the two MovieClips.
Because the x property of each circle is the only other feature that easily distinguishes
the two circles, that is what we will use. Starting at the second
mc.addEventListener(MouseEvent.CLICK,f);
and using
trace(mc.x);
you will see 200 in the Output panel, revealing that we have no code adding an event
listener to the leftmost circle. If you keep moving that trace(mc.x) line closer and
closer to the first line of code, you’ll pinpoint the exact line where the problem is
introduced. Above that second
mc=new MovieClip();
outputs the first circle’s x property. Below that line of code, it does not.
The only logical conclusion: The second mc=new MovieClip() is causing us to lose our
reference to the first circle.
trace(mc.x)
Errors That Do Not Trigger Error Messages
A reference is a value that points to a datum. In this situation, the value is mc, and
there are two data: the leftmost and rightmost MovieClips. Trying to use one reference to point to two MovieClips is problematic.
Now, why would that happen? We have mc referencing our first circle, and then as
soon as we assign mc to a new MovieClip, we lose our reference to the first circle.
Conclusion: One variable (such as mc) cannot reference two different objects at the
same time. Flash reassigns that one mc variable to point to the last assigned object.
One way to remedy this is to assign that listener to
leftmost circle.
mc
while it still references the
var mc:MovieClip;
mc=new MovieClip();
with(mc.graphics){
beginFill(0xaa0000);
drawCircle(0,0,20);
endFill()
}
addChild(mc);
mc.x=100;
mc.y=100;
mc.addEventListener(MouseEvent.CLICK,f); // mc is still the first created circle
mc=new MovieClip(); // mc is no longer the circle above.
with(mc.graphics){
beginFill(0xaa0000);
drawCircle(0,0,20);
endFill()
}
addChild(mc);
mc.x=200;
mc.y=100;
mc.addEventListener(MouseEvent.CLICK,f); // this mc is the second created circle
function f(e:MouseEvent):void{
trace(e.currentTarget.x);
}
13
14
Chapter 1 n Problematic Code: Debugging and Testing
If that appears to be an error you think no one would make, here is exactly the same
error in slightly different guise that someone posted on the ActionScript 3 Adobe
forum while I was writing the first draft of this page:
for (var io:int = 0; io < 4; io++) {
var opBtn:Btn_operator = new Btn_operator();
// there’s more code here irrelevant to this discussion
opBtn.addEventListener(MouseEvent.CLICK, pressOperator);
}
And then later in his code, the poster wanted to disable his
wrote, “I tried:
opBtn
instances and
opBtn.mouseEnabled=false;
but it didn’t seem to work.”
This is exactly the same error as my first example, but with the duplicate variables
“hidden” in plain sight in a for loop, which is the way this error typically occurs.
You create an object reference that is repeatedly used so the most recent object
reference overwrites the previous object reference. At the end of the for loop, that
reference refers to only the last created object. Appendix B will cover this issue
in more detail.
The point is that logical use of the trace() function allows you to pinpoint exactly
where your code fails. Once you know exactly where your code fails, you should
be able to determine why it fails and then determine what needs to be done to
correct it.
Testing and Experimenting
Unless you already know everything discussed here, it will be difficult to read this
book (or any other coding book) from start to finish and retain that information. I
encourage you to read a little, think a little, question everything, write some code,
and test your code to see the results.
Even when you’re in the midst of a major project, you can always save your files (you
don’t need to close them), open a new test FLA, and test code snippets without causing any problems with your main project. If you encounter or anticipate a problem
or you aren’t sure how to use some property, method, or anything else, opening a test
FLA can save you time, prevent debugging problems in your main project, and show
you how Flash will handle various situations.
Testing and Experimenting
For example, if you’re not sure whether new sounds can play after executing
SoundMixer.stopAll();
you can test it. This is so important that I’m going to say it again: If you’re reading
something in this book (or any ActionScript book) and you question how something
works, stop, open Flash (if it isn’t open already), write code, and test. If Flash is
already open, save your work, write code, and test.
You can’t do any major harm by testing, and if you’re anything like me, you’ll learn
coding more thoroughly and easily if you read a little, absorb some info, set aside
your reading material, write some code, and test your understanding of what you
just read. If you think of issues or situations not addressed in your reading material,
write the appropriate code and test how Flash handles it.
As I mentioned, you cannot cause any major harm by testing. The worst that can
happen is that Flash may crash. That’s a problem if you also have files with unsaved
work open, but if you save first, even that is no great disaster.
While we’re discussing saving your work, this might be a good time to remind you to
save your work frequently. Even less obvious but just as important: Every so often,
save your FLA file with a new name so that if one version gets corrupted, it won’t
take long to re-create your latest FLA.
Using a consistent naming convention (such as appending 01, 02, and so on to your
file name) is a particularly good idea. For example, if you start working on
spacegame.fla, your first save should be spacegame01.fla or spacegame_01.fla. When
you finish a project, you can delete the old versions or move them to a subdirectory
to keep your directories tidy.
Do not delete all your backup files until you have closed Flash, shut down your computer, restarted your computer, restarted Flash, and successfully reopened your files.
Once you know that you can reopen a FLA after a complete shutdown, you can be
confident that the just-opened version isn’t corrupted, and you can delete all the
backups. But don’t save over that last version. Again, save with a new file name if
you don’t have any backups.
In general, the more slowly you delete backups, the better. And the fewer backups
you delete, the better.
You can create backups to files other than your FLA files, but corruption of class files
is extremely unlikely (because class files are plain text files, not binary files), so it’s
not as important to back them up. However, if you’re about to make some major
15
16
Chapter 1 n Problematic Code: Debugging and Testing
changes to a class file and you want to maintain a copy of the previously working
version, save it using a naming convention that works for you.
I save class file backups using the same convention that I use for FLA files (by
appending _xx). I actually hadn’t done that very often in the past, but while writing
this book, I found I needed to create multiple versions of class files to show you how
I work. In later chapters you’ll see an explanation of this naming convention as it
applies to class files and how to use these (pseudo) class files.
In case you start doing this before reading those later chapters, the class files used in
your project will not include the appended_xx. So, for example, if you have a class
named Main.as and you want to save evolving versions of this class, you would save
Main_01.as, Main_02.as, etc. When you want to test one of those versions, you
would rename them Main.as.
Final Words on Debugging
Do yourself a favor: Format your code so you have indents and spacing that make
your code legible. Trying to debug unformatted code is like trying to sum a misaligned column of 25 numbers with pen and paper. It makes the task more difficult
and more time-consuming for no good reason and with no compensatory benefit.
Flash even has an auto-formatting option if you’re daring. But be careful if you use
auto-formatting. It can occasionally convert error-free code into problematic code.
You can always use the Undo command (Edit > Undo) to undo any problems caused
by auto-formatting, but for the most part, I find it easier to maintain good formatting
without using auto-format and risking the occasional problems.
When you use auto-format on code that contains more than 100 lines, it’s easy to
overlook an auto-format-induced problem. So, if I have a block of 20 or 30 lines of
code that need to be formatted in the midst of many more lines of code, I may open
a new FLA, paste the code in the Actions panel, and use auto-format. If the code
looks good, I’ll paste the formatted code over the unformatted code and then test to
make sure no errors were introduced in the formatting process.
Finally, if you see more than one error message, correct the first error and then
retest. That may sound like worthless advice, but often subsequent error messages
will resolve after you correct the first error.
Chapter 2
Avoiding Problematic Code
The previous chapter was about debugging code, and this chapter is about writing
code that minimizes errors that need to be debugged. Failure to understand scope as
it relates to Flash is a major cause of errors (which trigger error messages). In fact, I
believe it’s a certainty that failure to understand scope will lead to coding errors.
You could skip this chapter, generate errors, debug, and gradually learn about scope.
But reading this chapter will save you time. This chapter covers all you need to know
about Flash timeline scope, class scope, and function scope, thereby helping you
avoid most scope-related coding errors.
Before I start the discussion of scope, I want to discourage timeline coding. If you
avoid timeline coding, you will avoid a number of coding pitfalls and speed your
progress through this chapter.
Timeline Coding versus Class File Coding
There are some basic differences between timeline coding and coding in class files
(class coding, for short). There is no requirement that you do all of one or all of the
other. You can combine timeline and class coding in any Flash project.
But, in general, you should limit your timeline coding to little more than a stop(),
gotoAndPlay(), or gotoAndStop(). I will refer to any more ActionScript than one of
those three methods attached to a timeline as significant timeline coding.
For elementary projects (for example, 100 or fewer lines of code), significant timeline
coding, using one frame in one timeline to attach all your code, is acceptable. It’s not
necessarily good coding practice, but it’s acceptable in those circumstances.
17
18
Chapter 2 n Avoiding Problematic Code
For debugging and testing, you can add code anywhere you need for convenience.
Debugging and testing code will not need editing, extending, or maintaining—all
drawbacks of significant timeline coding.
For anything more complex, such as all but the simplest games, you should use class
files for your coding. It may seem more difficult and time-consuming to set up a
project using class files, but it’s much easier to debug, prevent bugs, and maintain
and extend a game when you’re using class files.
If you’ve ever added as little as 1,000 or 2,000 lines of code to a timeline, you’ve
probably encountered an Actions panel delay. It takes some versions of Flash Pro a
few seconds (or more) to display all the code that you’re likely to create when coding
a complex game, if that code is on a timeline. It takes even longer to scroll through
all that code looking for a section you want to edit.
Spreading code across more than one frame of a timeline and especially in more than
one timeline is a time-wasting mess and is strongly discouraged. Anyone who has
ever worked on a Flash file with significant bits of code spread over more than one
timeline understands the incredible amount of time that can be wasted looking for
pertinent code more than a few weeks after it was created.
The worst offenders I’ve seen (and I’ve seen hundreds of problematic Flash projects)
in the highly competitive worst Flash coding derby are templates sold by websites
such as www.entheosweb.com. Don’t be fooled into thinking you can get a headstart on a project by starting with a web template offered for sale. There may be some
well-coded templates sold online, but I’ve never seen one.
At a minimum, if you may one day want to edit a template, you should ask the vendor whether there is any significant timeline coding. If the answer is yes, do yourself
a favor and look elsewhere, or save your time and money and make your own.
Scope
ActionScript is an object-oriented programming (OOP) language. OOP languages use
objects to prevent data from being global. Each line of code in an OOP language is
within some scope. That is, data is confined to some object.
In Flash, those objects are MovieClips or classes. If you define something (for example, a variable) on a MovieClip’s timeline, the scope of that variable is the MovieClip,
and that variable is bound to the MovieClip. You can only access that variable directly
by using code on that MovieClip’s timeline or by using code that references both that
MovieClip and the variable.
Scope
If you define a variable in a class, that variable’s scope is the class. Again, that variable is bound to that class. You can directly access that variable only by using code in
that class or by using code that references that class and that variable, or an instance
of that class and that variable.
One significant benefit of this is that something defined in one scope won’t conflict
with anything outside of that scope. That’s why you can define an init (or any other)
function in each of your DisplayObject classes and be confident that it won’t conflict
with any other, even though their names are identical. (If you had two identically
named functions in the same scope, the compiler would generate a 1021 Duplicate
Function error.)
One significant drawback of scope is that something defined in one scope isn’t readily available in another scope. This is essentially another way to restate the previous
paragraph’s first sentence; the most important benefit is the same as the biggest
drawback.
How you view scope (as a frustrating drawback or a beneficial feature) depends on
how well you understand it. I don’t think scope and OOP are necessarily intuitive,
but once you understand scope, its benefits over sequential programming, especially
for complex applications such as games, become apparent.
MovieClip Scope
If you heed my earlier admonition about avoiding significant timeline coding and
you don’t have to edit anyone else’s poorly coded projects that contain significant
timeline coding, you can skip this subsection and advance to the “Class Scope” subsection. No code in this book will add significant code to timelines, so you don’t need
to understand MovieClip scope to read this book.
However, if, like me, you’re in the unfortunate category of souls who have to deal
with other coders’ problem projects, then I’m sorry for you, and I’m going to cover
everything you need to know about MovieClip scope. (And then I’ll never mention it
again in this book.)
When using timeline coding, you reference an object on that timeline using dot notation. For example, if you have a MovieClip on the main timeline stage with instance
name mc1 and you define a function on mc1’s timeline, like this:
function mc1F():void{
trace(this.name);
}
19
20
Chapter 2 n Avoiding Problematic Code
You would call
mcF
from the main timeline using:
mc1.mc1F();
For on-stage objects, if you fail to assign an instance name (by clicking the object to
select it and assigning a name property in the Properties panel), you won’t have an
easy way to reference it or any of its properties (variables) or methods (functions).
That is, there will be no easy way to call mc1F() from outside of mc1’s scope (or
timeline).
Notice that trace(this) will ouput the scope that contains the
Sometimes that can be helpful.
trace()
function.
You can chain MovieClip references using any number of MovieClips to reference an
object in one timeline from any other timeline. If you develop the (bad) habit of coding on many different timelines, you can end up with complex path (the reference
from one timeline to another) problems. Path problems are one reason (albeit a
minor one) why you should limit the code you place on different timelines.
For example, you could end up with something like the following if a function named
were defined on a MovieClip timeline ggchild1 that was a child of gchild1 that
was a child of child1 that was a child of parent1:
f1()
parent1.child1.gchild1.ggchild1.f1();
And if you wanted to call a function named parent1F() defined on the
line from the ggchild1 timeline, you would use:
parent1
time-
parent.parent.parent.parent1F();
This is awkward, and you should avoid this type of coding by minimizing code
placed on more than one timeline. In addition, for complex projects such as games,
it will be easier to code using class files.
But if you do find yourself in a situation where you need to reference one timeline
object from another timeline and you don’t know the path, you can use the trace()
function to reveal the correct path.
In fact, you can reveal the path to any
MovieClip’s timeline:
timelinePathF();
function timelinePathF():void {
if (this.name.indexOf(“root“)>-1) {
trace(“MovieClip(root)“);
} else {
var mcBool:Boolean;
MovieClip
by adding this code to that
Scope
var s:String=this.name;
var mc:MovieClip=MovieClip(this.parent);
while (mc.name!=“root1“) {
if(mc.parent is MovieClip){
mcBool = true;
s=mc.name+”.”+s;
mc=MovieClip(mc.parent);
} else if(mc.parent is Loader){
mcBool = false;
break;
}
}
if(mcBool){
s=“MovieClip(root).”+s;
} else {
s = “MovieClip(getChildByName(“+mc.parent.name+“).content).”+s;
}
trace(s);
}
}
If you add the same code to a second MovieClip’s timeline, you can see how to
reference an object that is defined on one timeline using code that is on the other
MovieClip’s timeline. For example, if you have a function f() defined on the timeline
of a MovieClip mc_a6 with timeline path:
MovieClip(root).mc_a1.mc_a2.mc_a3.mc_a4.mc_a5.mc_a6
and you want to reference
path:
f()
from the timeline of a
MovieClip mc_b2
with timeline
MovieClip(root).mc_a1.mc_a2.mc_b1.mc_b2
count back to the first common ancestor (mc_a2) of the two MovieClips and then
advance to the timeline with f(). Therefore, from the mc_b2 timeline, this references
the mc_b2 MovieClip, this.parent references the mc_b1 MovieClip, this.parent.parent
references the mc_a2 MovieClip, and going forward to f() yields:
this.parent.parent.mc_a3.mc_a4.mc_a5.mc_a6.f();
which is one way to reference the function
f()
in
mc_a6
from
mc_b2.
This is an example of a relative path. That is, the path is relative to both mc_a6 and
There is always a relative path because two MovieClips always share at least one
common ancestor, MovieClip(root).
mc_b2.
21
22
Chapter 2 n Avoiding Problematic Code
You can also use an absolute path—a path that is independent of the code’s location.
An absolute path starts with the main timeline (root) and then uses dot notation to
indicate the MovieClip of interest.
An example of an absolute path is:
MovieClip(root).mc_a1.mc_a2.mc_a3.mc_a4.mc_a5.mc_a6
You can place that code on any timeline anywhere in the same SWF that contains
those MovieClips, and it will correctly reference the MovieClip mc_a6 (as long as it
exists when the code referencing it executes).
An absolute path is easier to determine than a relative path, but there is a major
problem with absolute paths: The absolute path will change if the SWF containing
it is loaded into another SWF. That is a major problem because when you’re creating
more complex games and completing the work on one SWF that will be loaded into
another, you’ll find that the completed SWF needs more work to adjust all absolute
paths.
And, once you fix that absolute path so the loaded SWF works correctly when
loaded, it won’t work correctly if tested as a stand-alone SWF (in other words, without being loaded). Those path problems are expected when using absolute paths and
are something you’ll probably contend with if you edit projects with significant timeline coding.
You can use timelinePathF() to determine the correct path even when the SWF containing that function is loaded into another SWF. I’m sure there are situations where
timelinePathF() will fail to reveal the correct path, but by using the trace() function
you should be able to extend timelinePathF() to work for any situation.
Whether you’re using an absolute or a relative path, MovieClip mc_a6 has to exist
when code trying to reference it executes. If f() is an anonymous function, not only
must the MovieClip mc_a6 exist, but the frame containing the definition of f() must
also play before the frame that contains code that tries to reference f().
Because named functions are compiled and ready for use before the frame that contains the function executes (and even if it never executes), the previous sentence
doesn’t apply to named functions.
An anonymous function has the form:
var f:Function = function():void{
...
}
Scope
A named function has the form:
function f():void{
...
}
Further, if you’re trying to reference a variable or an object such as a MovieClip, the
frame containing it must play before you can reference the variable or object. Trying
to reference an on-stage object before the object exists (in other words, before the
first frame containing that object plays) is a much more common error than faulty
function references, and it requires more work to remedy.
You’ll typically encounter this when code on one frame uses gotoAndPlay() or
gotoAndStop(), and an object in the target frame needs to be referenced using code
on the frame that contains the goto function. The usual remedy for that is to add an
Event.RENDER listener and invalidate the stage.
For example, if you have a textfield tf that exists only on Frame 3, the following
code, on any other frame of the same timeline, will be able to reference tf.
informs the flash player to dispatch an Event.RENDER event to each
display object that has an Event.RENDER listener the next time the player renders the
display.
stage.invalidate()
stage.invalidate();
var cFrame:int = currentFrame;
addEventListener(Event.RENDER,f);
gotoAndStop(3);
function f(e:Event):void{
tf.text = “The previous frame was “+cFrame;
}
Instead of wasting time discussing how to deal with that remedy and the other problems caused by using significant timeline coding, I’ll just tell you to save yourself the
aggravation and avoid it.
Class Scope
It is important to understand class scope because failure to understand it leads to
many errors and much frustration. The concept is simple, but understanding it
seems difficult for many people. There isn’t much to explain, so read (and possibly
reread) the next paragraph carefully.
The scope of everything defined in a class file is that class. That is, the scope of every
variable and every function defined in a class file is that class.
23
24
Chapter 2 n Avoiding Problematic Code
For example, if you define a variable var1 in a class Main, the scope of var1 is Main.
That means you cannot reference var1 outside of the class Main except when you
explicitly choose to expose it to other scopes (or classes). The next chapter will discuss how you expose variables and functions defined in one class to other classes.
Function Scope
When you declare a property using the var keyword inside a function body, that
property is defined only within that function body and only while the function is
executing. That is, the property’s scope is the function. The property expires (in
other words, does not exist) when the function completes execution. Even if you
immediately recall the same function, the property’s value from the previous call is
irretrievable.
Failure to understand function scope is the source of many errors. For example:
package {
public class TestClass {
public function TestClass() {
// stringVar is local to the TestClass constructor
var stringVar:String; = “string 1“;
init();
}
private function init():void{
// This will trigger an 1120 error because stringVar is undefined
// here.
trace(stringVar);
}
}
}
will generate an error, 1120: Access of Undefined Property stringVar.
If you want to define a property within a function body and you want it to be available outside that function, you must declare the property outside of all functions.
package {
public class TestClass {
// stringVar declaration
private var stringVar:String;
public function TestClass() {
// stringVar definition
stringVar = “string 1“;
init();
}
Scope
private function init():void{
trace(stringVar);
}
}
}
Function scope is the same whether your function is defined in a class or on a timeline, and it’s the same whether your function is anonymous or named.
This is such a common problem that I’m starting to think everyone eventually
encounters this issue. So, I think it is worth repeating. If you define a variable to be
local to that function (by prefixing with the keyword var), that variable and that variable’s value are only available inside that function while that function is executing.
There is a similar issue with functions declared locally. For example, in the following
code, f2() is local to f1(). If you try to call f2() outside of the f1() function body,
you will trigger an 1180: Call to a Possibly Undefined Method f2 error.
function f1():void {
// Trying to call f2 from here will trigger a runtime 1006 error: value is not
// a function because f2 is not defined, yet.
var f2:Function = function(){
};
// This will work because the call is from within the f1 function body.
f2();
}
// Trying to call f2 from here will trigger an 1180 error no matter when or how many
// times you call f1.
With anonymous functions, just like with variables, you can declare the nested function outside of the nesting function body if you want to make the nested function
available outside of the nesting function. For example, the following won’t trigger any
errors:
var f2:Function;
function f1():void {
f2 = function(){
};
}
f1();
f2();
25
26
Chapter 2 n Avoiding Problematic Code
However, because you cannot declare a named function, you should never nest a
named function like the following:
function f1():void{
// You can only reference f2() from here
function f2():void{
}
// or here - both within f1()
}
f1();
// But not here. This will trigger an 1180 error.
f2();
There is nothing (other than defining f2() outside of f1()) you can add to that code
to make f2() available outside of f1(). You should un-nest those functions.
Un-nesting those functions has no drawbacks and has two major advantages: You
can call f2() from outside of f1(), and your code is easier to read and debug.
function f1():void{
}
function f2():void{
}
Further, there is no benefit to nesting a named function, so you should never do so.
Chapter 3
Writing Class Code
By convention, class files usually follow the same general format:
1. Package designation
2. Import statements
3. The class declaration
4. Class-scoped (that is, not local to a function) variable declarations
5. The class constructor
6. Other class functions
In this chapter we’ll start writing class code covering these six components, expand
on the previous discussion of class scope, and discuss some of the ways that variables,
functions, and objects defined in one class can be exposed to and accessed by other
classes.
Open Flash, start a new ActionScript 3.0 document, and save your FLA into a new
directory using a file name more meaningful than the default Untitled-n.fla that Flash
offers. Then click on an empty part of the stage (which should be the entire stage at
this point) or the pasteboard and, in the Properties panel, enter a document class
name (for example, Main). Click the pencil icon (Edit Class Definition), and your
default class file should open (see Figure 3.1). Save this with the suggested name
(the only one that should be given to this file), Main.as.
27
28
Chapter 3 n Writing Class Code
Document
class name
main entered
Edit
Class
Definition
icon
Figure 3.1
Properties panel with document selected.
Source: Adobe Systems Incorporated.
If you have an older Flash Pro CS version, your basic Main class won’t be created
for you when you click the pencil icon, and you may see a warning like the one in
Figure 3.2.
Figure 3.2
Warning displayed after clicking the Edit Class Definition icon in some older Flash Pro versions.
Source: Adobe Systems Incorporated.
Writing Class Code
In that case, Flash also won’t suggest the correct file name, so copy the following into
a new class file (File > New > ActionScript File > OK) and save it as Main.as:
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
// constructor code
}
}
}
The first line of all AS3 class files contains the keyword package, which indicates
which directory (relative to the location of the FLA, not the SWF) contains the
class. If a class is in the same directory as the FLA, your package designation should
look like the previous code. If a class is in a subdirectory C of the FLA-containing
directory, your code should look like this:
package C{
...
}
And if you wanted to save your class in a subdirectory C2 of the subdirectory C1,
your class package would indicate that by using:
package C1.C2{
...
}
After the package designation, needed classes are imported. You can add class source
paths in your Publish Settings, but for simplicity we’ll assume that you have only the
default Flash class path.
Therefore, you’ll need to import every class that isn’t in the same directory with your
FLA and used in Main. Because we are extending the MovieClip class and no other
class is used in Main (yet), the MovieClip class is the only one you need to import.
Because the MovieClip class is part of the flash.display package (that is, the Adobecreated MovieClip class is in the flash/display subdirectory of the default class path),
import flash.display.MovieClip is used.
If you know you need to import the MovieClip class, but you don’t know or can’t
remember which package it’s in, open the Flash help files, navigate to the ActionScript 3.0 classes, and then navigate to MovieClip. The topmost line will indicate the
MovieClip package. The same is true for all other Flash classes. (I will cover this in
more detail in Chapter 5, “Using the Flash API and Starting a Flash Game.”)
29
30
Chapter 3 n Writing Class Code
The third line of code declares that the class is public and the name is Main, and
it indicates that the class extends the MovieClip class. Because Main extends the
MovieClip class, you can use all the MovieClip properties, methods, and events (after
you import the needed event classes). We’ll cover the public attribute in the next
section.
The fourth line is called the class constructor. It is a function that executes each time
a class member (or instance) is created. Because Main is a document class, the constructor will execute only once each time your application (for example, a SWF)
starts.
Notice that three names exactly match each other: the class name, the class constructor, and the name of the file. If the class name and file name don’t match,
you’ll trigger a Flash 5008 error (see Figure 3.3).
Figure 3.3
Error trigged by a class name/file name mismatch.
Source: Adobe Systems Incorporated.
If the constructor doesn’t match the class name and file name, there won’t be an error. In
fact, you don’t even need to define a constructor. If there’s no code that needs to be
executed as soon as a class instance is created, there’s no need for a constructor.
So, if you mistype a constructor name, Flash has no way of knowing you intended
that function to be a constructor and will assume that you created some other function, and you won’t trigger an error message.
The only problem you’ll see is that the code in your misnamed constructor won’t
execute when a class instance is created. A trace(this) in your misnamed constructor
will confirm that the code isn’t executing and should prompt you to search for a constructor name typo.
Internal, Private, Protected, and Public Properties
Also, if the package designation and the class file’s location don’t match, you may or
may not see an error message. If Flash attempts to compile the class file, you will see
a Flash 5001 error (see Figure 3.4).
Figure 3.4
Error trigged by having a package/directory location mismatch.
Source: Adobe Systems Incorporated.
But Flash may not attempt to compile the class file, so no error message will be triggered. For example, if you have a document class of com.Main, and there’s no Main.as
in the com directory or there’s no directory named com, Flash won’t find Main.as
and won’t attempt to compile it.
For coders uncomfortable with OOP, a common source of problems is trying to
access objects defined in one class from another class. I’m going to show you some
ways you can do that.
First, you’ll need to understand method and property attributes. Four attributes can
be designated for a method, and the same four can be designated for a property—
internal, private, protected, and public. The following section will explain these.
Internal, Private, Protected,
and Public Properties
When using class coding, you can reference any public method (function) or property (variable) using dot notation. You cannot directly reference any property or
method that is designated as private unless you’re within the class scope (in other
words, your code is in the same class). And there are two other attributes you can
assign to class properties and methods: internal and protected.
Designating a property or method as internal makes it available to anything within
the same package, and assigning a property or method as protected restricts availability to the class and its subclasses (if there are any). internal is the default attribute, so
31
32
Chapter 3 n Writing Class Code
if you assign no attribute, the property or method will behave as if it has the internal
attribute. That is, it will be available to any other class in the same package.
Now, saying a property or method is available to any other class or any class in the
same package and so on doesn’t change the fact that ActionScript is an OOP language. That is, the property or method is still defined only in the scope of the class.
And while it may be available outside the class, you still have to use the correct (dot
notation) code to reference it correctly.
So, for example, if in the class
C1
you have:
C1.as
package {
// This is the class declaration
public class C1 {
// The line below is, by default, the same as
// internal var var1:String=“from C1”;
var var1:String=“from C1”;
public function C1() {
// This is the class constructor and must match the name of the
// file and the class name if you want this code to execute each
// time a class instance is created.
}
}
}
and you want to reference
class Main—you could use:
var1
from outside the scope of
C1—for
example, in the
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
// Create an instance (c1) of C1
var c1:C1 = new C1();
// Because var1 is an internal property you can access it outside
// the scope of C1 as long as Main is in the same package as C1
trace(c1.var1);
}
}
}
Also, because
C1 into Main.
C1
is in the same package as
Main,
you don’t need to explicitly import
Internal, Private, Protected, and Public Properties
If C1 isn’t in the same package as Main, you must use the public attribute (not the
default internal), and you must explicitly import C1 into Main.
Before you try the following code, delete C1.as from the FLA’s directory. (See the
“Problems Related to Class File Use” section in Appendix B, “Errors That Do Not
Trigger an Error Message.”) This code must be in a file named C1.as that is in a subdirectory named C.
C1.as in subdirectory C
package C {
public class C1 {
public var var1:String=“from C.C1”;
public function C1() {
}
}
}
Main.as
package {
import flash.display.MovieClip;
// import C.C1
import C.C1;
public class Main extends MovieClip {
public function Main() {
// create an instance (c1) of C1
var c1:C1 = new C1();
// because var1 is public property, you can access it from any
// class that imports C.C1
trace(c1.var1);
}
}
}
Any variable accessed this way can also be redefined the same way, and that’s not
always a good thing. For example, in Main, instead of just checking c1.var1, you
could reassign it.
c1.var1 = “from Class B”;
There are two main reasons why you might not want to use a public variable and
thereby allow this direct access to your C.C1 variable var1. First, you may want var1
to always be exactly “from C.C1” (in other words, you want it to be read-only). Second, you might choose to allow it to be changed, but you might always want it to
indicate that it’s from C1.C (in other words, you want to error check). These are the
two main reasons why you should know about getters and setters.
33
34
Chapter 3 n Writing Class Code
Getters and Setters
You might not have realized that you’ve been using a getter every time you check
(get) a MovieClip’s x property and using a setter every time you assign (set) a
MovieClip’s x property. All MovieClip properties that you have checked and assigned
with dot notation are examples of how to use getters and setters. Adobe wrote the
MovieClip class code that defined those getters and setters.
Here’s how to create your own getter:
C1.as
package {
public class C1 {
private var _var1:String=“from C1”;
public function C1() {
}
public function get var1():String{
trace(“getter working”);
return _var1;
}
}
}
Notice that I changed the variable to _var1 and made it private. It can’t be accessed
from anywhere outside of the class C1 directly (by referencing a C1 class member).
But I did define a public function get var1() that returns the value of _var1.
Now, if in
Main
I try the following, I’ll see the indicated
trace()
function output.
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
var c1:C1 = new C1();
// “getter working” followed by “from C1” is traced
trace(c1.var1);
// If you uncomment the line below, a 1059 error will be
// triggered. (See Figure 3.5.)
// c1.var1 = “from B”;
}
}
}
Getters and Setters
Figure 3.5
Error triggered by trying to write to a read-only property.
Source: Adobe Systems Incorporated.
Here’s how to add a setter that does error checking and makes
var1
read and write:
C1.as
package C{
public class C1 {
private var _var1:String=“from C1”;
public function C1() {
}
public function get var1():String{
return _var1;
}
public function set var1(s:String):void{
// allow anything to be appended to _var1
if(s.indexOf(“from C1”)==0){
_var1 = s;
} else {
trace(“Invalid assignment to _var1 attempted”);
}
}
}
}
Now, if in
Main
I try the following, I’ll see the indicated
trace()
function output.
35
36
Chapter 3 n Writing Class Code
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
var c1:C1 = new C1();
// “getter working” followed by “from C1” is traced
trace(c1.var1);
// “Invalid assignment to _var1 attempted” is traced and var1 is
// not reassigned.
c1.var1 = “from B”;
// will reassign _var1
c1.var1 = “from C1 reassigned in Main”;
}
}
}
If you have two other classes C2 and C3 and you want to reference var1 from C1 in
each of those, you would use similar code in each class. But, the two var1 properties
aren’t the same, and they probably won’t have the same value. Each is a property of
its class instance, and you’ll be creating one instance in C2 and a different instance in
C3. Even if you call them both c1, they’re not in the same scope, and therefore they
don’t conflict with each other and aren’t the same instances.
Most of the time, that’s exactly what you want: Each C1 instance is distinct from
every other C1 instance. But there are ways to create a property in one class, assign
it a value, and enable all other classes in your project to access that same property.
One way is to use a static property.
But before I get to static properties, I want to expand on the previous Main and C1
example by showing three ways to communicate from Main to C1. The previous sample code showed you how to communicate from C1 to Main.
Among the ways you can communicate from Main to C1, you can pass a variable from
Main in the C1 constructor, you can use a public method in C1 to pass a variable, and
you can use an event dispatcher in Main to communicate with an event listener in C1.
Example 1: Passing a Variable from Main to C1
via the C1 Constructor
Main.as
package {
import flash.display.MovieClip
Getters and Setters
public class Main extends MovieClip {
private var mainVariable:String = “from Main”;
public function C2() {
var c1:C1 = new C1(mainVariable);
}
}
}
C1.as
package {
private var mainVar:String;
public class C1 {
public function C1(_mainVar:String) {
mainVar=_mainVar;
}
}
}
Example 2: Passing a Variable from Main to C1
Using a C1 Public Method
Main.as
package {
public class Main {
private var mainVariable:String = “from Main”;
public function Main() {
var c1:C1 = new C1();
c1.getVarF(mainVariable);
}
}
}
C1.as
package {
private var mainVar:String;
public class C1 {
public function C1() {
}
public function getVarF(_mainVar:String){
mainVar = _mainVar;
}
}
}
37
38
Chapter 3 n Writing Class Code
Example 3: Using an Event Dispatcher
Main.as
package {
import flash.display.MovieClip;
import flash.events.Event;
public class Main extends MovieClip {
public function Main() {
var c1:C1 = new C1();
c1.dispatchEvent(new Event(“customE”));
}
}
}
C1.as
package {
// If you fail to import the Event class, you will trigger a 1046 error. (See
// Figure 3.6.)
import flash.events.Event;
import flash.events.EventDispatcher;
// C1 must extend a class that has an addEventListener method or you’ll trigger
// a 1061 error. (See Figure 3.7.)
// If you do not know which classes have an addEventListener method, use
// the Flash help files > ActionScript 3.0 Language reference > index >
// addEventListener. The classes with this method are listed.
public class C1 extends EventDispatcher{
public function C1() {
this.addEventListener(“customE”, customF);
}
private function customF(e:Event):void {
trace(e.type+“ has been dispatched from in Main”);
}
}
}
static Properties
Figure 3.6
Error triggered by trying to reference an object (e) that is a member of an unavailable (to C1) class (Event).
Source: Adobe Systems Incorporated.
Figure 3.7
Error triggered by trying to apply a method to C1 instances that is not part of the C1
class.
Source: Adobe Systems Incorporated.
static Properties
properties are assigned to a class, not a class instance. When you use the new constructor, you create class instances. When you want to reference a static property,
you reference the class and property using dot notation without using a constructor.
static
To show how this works, start by creating a document class Main. Click an empty part
of the stage or pasteboard and type Main in the Document Class textfield in the
Properties panel. Then click the pencil icon (Edit Class Definition) to the right of
that textfield. Replace the Main class code created by Flash with the Main class below
and save the file as Main.as.
39
40
Chapter 3 n Writing Class Code
Main.as
package {
import flash.display.MovieClip;
import flash.display.DisplayObject;
public class Main extends MovieClip {
public function Main() {
// Glo and TestClass need to be created before testing the Main
// class
var glo:Glo = new Glo(MovieClip(this.root));
var tc:TestClass = new TestClass();
}
}
}
Then create a
code.
Glo
class (click File > New > ActionScript 3.0 Class) with the following
Glo.as
package {
import flash.display.MovieClip;
public class Glo {
// bal is a static property of type object.
public static var bal:Object={};
public function Glo(mc:MovieClip) {
bal.root = MovieClip(mc.root);
}
}
}
And finally, create a third class (TestClass) to test whether you can reference bal.root
in another class.
TestClass1.as
package {
import flash.display.MovieClip;
public class TestClass1 {
private var rootVar:MovieClip;
public function TestClass1() {
rootVar = Glo.bal.root;
trace(“test”,rootVar);
}
}
}
static Properties
This Glo.bal code to create pseudo-global properties isn’t my code. I saw it or something very similar on the Internet years ago, when Adobe first released Flash Pro CS3
and ActionScript 3.0. It’s probably Grant Skinner’s code, but I’m not certain.
The key point isn’t that you should use pseudo-global properties or whose code that
is. The key point is that the above shows one way to share the same property across
any number of classes using a static property. You can create TestClass2, TestClass3,
and so on, and they can all reference the same property.
By the way, your document class must extend the Sprite or MovieClip class. If your
document class extends the Sprite class instead of the MovieClip class, you’ll save an
insignificant amount of memory. However, if your document class extends the Sprite
class, you cannot use any timeline code (for debugging and testing), and even a single
space or carriage return anywhere in the Actions panel will trigger an 1180 error
(see Figure 3.8).
Figure 3.8
Error triggered if the document class extends the Sprite class and there is anything in the Actions panel.
Source: Adobe Systems Incorporated.
If you’re testing the above three classes when your document class extends the Sprite
class instead of the MovieClip class, you’ll need to replace all MovieClip references in
all three classes with Sprite references.
41
42
Chapter 3 n Writing Class Code
Internal, Private, Protected, Public, and
Static Methods
There’s no significant difference between applying internal, private, protected, and
public attributes to properties and applying the same attributes to methods. You use
the same syntax, and the same principles apply.
Example 1: public Method
C1.as
package {
public class C1 {
public function C1() {
}
public function f1():void{
trace(“f1() in C1”);
}
}
}
C2.as
package {
public class C2 {
public function C2() {
var c1:C1 = new C1();
c1.f1();
}
}
}
// create an instance (c1) of C1
Example 2: static public Method
package {
public class C1 {
static public function f1():void{
trace(“f1 in C1”);
}
}
}
Singleton Class
C2.as
package {
public class C2 {
public function C2() {
C1.f1();
}
}
}
Singleton Class
This code is definitely from Grant Skinner and provides another way (in addition to
using a class with static methods and properties) to share the variables and functions across any number of classes.
SingletonDemo.as
package {
public class SingletonDemo {
private static var instance:SingletonDemo;
private static var allowInstantiation:Boolean;
public static function getInstance():SingletonDemo {
if (instance == null) {
allowInstantiation=true;
instance = new SingletonDemo();
allowInstantiation=false;
}
return instance;
}
public function SingletonDemo():void {
if (! allowInstantiation) {
throw new Error(“Error: Instantiation failed: Use
SingletonDemo.getInstance() instead of new.”);
}
}
}
}
To use a singleton class, you execute:
var sd:SingletonDemo = SingletonDemo.getInstance();
In every class in which you execute SingletonDemo.getInstance(), the same class
instance is returned. That is, if you execute that SingletonDemo.getInstance() in different classes, they will all return the same class instance.
43
44
Chapter 3 n Writing Class Code
Because you can create only a single class instance using this construct, it’s called a
singleton class. Here’s an example that shows how you could use this class to share
data between Main and C1. Data is the singleton class.
Main.as
package {
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
private var test1:String = “test”;
public function Main() {
var c1:C1 = new C1();
// Because this is the first time Data.getInstance() is executed,
// this creates the (only) Data instance
var d:Data = Data.getInstance();
// Assign a variable’s value
d.data1 = “HI”;
// Notify c1 that data1 has had its value changed.
c1.dispatchEvent(new Event(“data1ChangeE”));
}
}
}
C1.as
package {
import flash.events.Event;
import flash.events.EventDispatcher;
public class C1 extends EventDispatcher{
private var dataInstance:Data;
public function C1() {
// Retrieve the Data instance created in Main. It does not matter
// what name you call the Data instance because the name is local
// to C1. What matters is the instance itself is the same.
dataInstance = Data.getInstance();
this.addEventListener(“data1ChangeE”, data1ChangedF);
}
private function data1ChangedF(e:Event):void {
// Retrieve the data1 property of the Data instance.
trace(dataInstance.data1);
}
}
}
dynamic Class
Data.as
package {
public class Data {
private static var instance:Data;
private static var allowInstantiation:Boolean;
private var _data1:String;
public static function getInstance():Data {
if (instance == null) {
allowInstantiation=true;
instance = new Data();
allowInstantiation=false;
}
return instance;
}
public function Data():void {
if (! allowInstantiation) {
throw new Error(“Error: Instantiation failed: Use
Data.getInstance() instead of new.”);
}
}
public function set data1(s:String):void {
_data1=s;
}
public function get data1():String {
return _data1;
}
}
}
dynamic Class
Normally, when a class instance is created, you cannot assign properties to
that instance unless they are declared in the class and have a suitable (or at least
not private) attribute. If you try, you will trigger a 1119 error (see Figure 3.9). For
example:
Main.as
package {
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
private var test1:String = “test”;
public function Main() {
45
46
Chapter 3 n Writing Class Code
var c1:C1 = new C1();
// An attempt to assign var1 to c1 will trigger a 1119 error.
c1.var1 = “HI”;
}
}
}
C1.as
package {
public class C1{
public function C1() {
}
}
}
Figure 3.9
Error triggered if you try to assign an undeclared property.
Source: Adobe Systems Incorporated.
However, if you use the class modifier dynamic, you can assign properties undeclared
in C1 outside of C1. For example, changing C1 to the following will allow var1 to be
applied to c1 in Main.
C1.as
package {
dynamic public class C1{
public function C1() {
}
}
}
Chapter 4
What You Should Know
While developing your game, you can easily learn much of what you need to know to
encode games just by using logic and knowing what document to use as a reference.
Those topics are covered in the next chapter.
But some of what you should know is difficult to find unless you know it exists. I’ll
briefly cover those in this chapter, listed in alphabetical order. You need to know
they exist so that when the need arises, you remember these classes are available for
your use. Then you can turn to the Flash API (discussed in the next chapter) to learn
how to use them.
Arrays
When you need to save and retrieve a sequence of data, you might want to use a
numerically indexed Array (or Array, for short). Much of the time, you will save and
retrieve one variable at a time. For example:
var mc:MovieClip = new MovieClip();
creates one
MovieClip
that is referenced by
mc.
But what if you create 100
MovieClips?
// for loops are another one of those concepts you need to know and are
// mentioned below in the Loops section.
for(var i:int=0;i<100;i++){
var mc:MovieClip = new MovieClip();
}
47
48
Chapter 4 n What You Should Know
How can you reference each of those 100 MovieClips? One way is to use an Array. For
example:
var mcA:Array = [];
for(var i:int=0;i<100;i++){
var mc:MovieClip = new MovieClip();
mcA.push(mc);
}
now contains a reference for each of the 100 MovieClips. For example, mcA[0]
references the first created MovieClip in that for loop. In fact, because of the code I
used, it is the only way to reference that MovieClip and all of the other MovieClips
(other than the last one created). mc still references that last MovieClip even after the
for loop completes.
mcA
Better coding to create 100
MovieClips
stored in
mcA
would be:
var mcA:Array = [];
for(var i:int=0;i<100;i++){
// push is method of the Array class that adds elements to an array.
mcA.push(new MovieClip());
}
because Flash then isn’t burdened with the creation and disposal of 99
mc
references.
There is a useful relationship between the String class and the Array class. Any String
instance can be converted to an Array instance using the split() method of the
String class, and any Array instance can be converted to a String using the join()
method of the Array class.
For example, I had a project where I wanted to create an Array containing each letter
in the alphabet. This would be boring, time-consuming, and prone to typos:
var alphabetA:Array = [“a”,”b”,”c”,...,”z“];
If I tried doing that, I would probably end up with two commas next to each other
and/or mismatched quotes.
Instead, I used the following, which is much more efficient and less likely to need
debugging because of typos:
var alphabetS:String = “abcdefghijklmnopqrstuvwxyz“;
var alphabetA:Array = alphabetS.split(““);
When you want to convert an
Array
instance into a string, use the
join()
// join is method of the Array class that concatenates the array elements
// in a string separated by the join parameter. In this example, there is
method:
Arrays
// no parameter used.
var s:String = alphabetA.join(““);
This should create a string
s
that is identical to
alphabetS.
By default, assignment of an Array to another Array creates a “deep” copy. That is,
changes to one Array affect the other Array. For example:
var a:Array = [1,2];
var b:Array = a;
a.push(3)
b.push(4);
trace(a); // traces 1,2,3,4
trace(b); // traces 1,2,3,4
To create a copy of an Array that you can manipulate independently from the original, use the slice() or concat() method. The concat() method is faster.
var a:Array = [1,2];
var b:Array = a.concat();
// or var b:Array = a.slice();
a.push(3)
b.push(4);
trace(a); // traces 1,2,3
trace(b); // traces 1,2,4
It isn’t unusual to have a list of numbers or objects from which you want to select an
element at random repeatedly, without selecting the same number or object more
than once. This is analogous to shuffling a deck of cards.
For those situations, adding the numbers or objects to an Array and then using the
shuffle() function (an implementation of the Fisher-Yates shuffle method) as follows
is ideal.
function shuffle(a:Array) {
var j:int;
var i:int;
var e:*;
var len:int = a.length;
for (i = len-1; i>=0; i--) {
// Check Randomization below in the Math Class if you are
// unfamiliar with Math.random()
j=Math.floor((i+1)*Math.random());
e = a[i];
a[i] = a[j];
a[j] = e;
}
}
49
50
Chapter 4 n What You Should Know
To use shuffle(), make no changes to the function. Simply pass the Array you wish
to randomize to shuffle() and then iterate through the shuffled function. For
example:
var Array1:Array=[0,1,2,3,4,5,6,7,8,9];
shuffle(Array1);
// Array1 is now randomized. If you iterate through the
// elements of the now randomized Array1, you will generate 0 to 9 in
// random order without repeats.
trace(Array1);
Associative Arrays
In ActionScript, an associative Array is another term for an Object and is somewhat
similar to an Array in that Arrays can be thought of as an ordered collection of references (from the first Array element to the last), while associative Arrays are an unordered collection of references. In other programming languages, associative Arrays are
also called maps or hashes.
No matter what they are called, associative Arrays associate strings and values (while
regular Arrays associate non-negative integers with values). The strings are used to
point to a value and are called keys. For example:
var mcObj:Object = {};
for(var i:int=0;i<100;i++){
var mc:MovieClip = new MovieClip();
mc.name = “mc_“+i;
mcObj[mc.name] = mc;
// mcObj[mc] = mc; will not work the way you want. Check the
// Dictionary class.
}
trace(mcObj[“mc_0“],mcObj[“mc_0“].name); // [object MovieClip] mc_0
creates and defines an associative
value is a reference to the actual
an associative Array, you can use:
that uses MovieClip names as keys, and the
MovieClip that has that name. To iterate through
Array
for(var s:String in mcObj){
// mcObj[s] is using Array notation. See the next section.
trace(“The key is”,s,”and the value is”,mcObj[s]);
}
Array Notation
You use Array notation to coerce Flash to recognize a string as an object. For example, if you have on-stage MovieClips mc1, mc2,…mc25, this is in the scope of the
BitmapData
25 MovieClips, and you want to add a
can use:
MouseEvent.CLICK
listener to them all, you
for (var i:int=1; i<=25; i++) {
this[“mc“+i].addEventListener(MouseEvent.CLICK,clickF);
}
function clickF(e:MouseEvent):void {
trace(e.currentTarget.name);
}
Similarly, if you wanted to create variables
you could use:
a,b,c,...,z
and initialize them all to 0,
var alphabetS:String = “abcdefghijklmnopqrstuvwxyz“;
var alphabetA:Array = alphabetS.split(““);
for(var index:int=0;index<alphabetA.length;index++){
// The outer brackets are an example of using Array notation to coerce
// a string to an object, in this case, a variable. The inner brackets
// display the standard Array access operator.
this[alphabetA[index]] = 0;
}
trace(this.a,this.b,this.z);
When I first wrote that code, I used i (my usual) as the iteration variable in this for
loop and triggered a script timeout error because i was resetting the iteration variable
i (to be 0) inside the for loop.
BitmapData
Any time you need to access, manipulate, or create pixel data, think of the BitmapData
class. draw(), getPixel32(), setPixel32(), and hitTest() are the methods you should
first study after checking the constructor.
Among other things, with those methods you can create a color detector for your
DisplayObjects and create a shape-based hit test.
For example, here’s a simple color picker for a DisplayObject with a top-left registration point. In this example, the clicked pixel in mc is the traced color.
var bmd:BitmapData = new BitmapData(mc.width,mc.height,true,0x00000000);
var mat:Matrix = mc.transform.concatenatedMatrix;
mat.tx=0;
mat.ty=0;
bmd.draw(mc,mat);
stage.addEventListener(MouseEvent.CLICK,clickF);
function clickF(e:MouseEvent):void{
51
52
Chapter 4 n What You Should Know
trace(bmd.getPixel32(e.stageX-mc.x,e.stageY-mc.y).toString(16));
}
And here’s a more complex application for a shape-based hit-detection class. This
class is an example of a singleton class. To use it, encode:
// import the needed class
import com.kglad.HitF;
// Create a class instance using the following line of code. This ensures
// only one class instance is created in your game.
var hitF:HitF = HitF.getInstance();
// To test for a shape-based hit between two objects mc1 and mc2, use:
if(hitF.hit(mc1,mc2)){
// do whatever
}
The class itself follows. It contains a lot of code that may not be needed in specific
situations.
Because this class allows for any number of object nestings and any registration
points for those nestings and it minimizes the number of created bitmaps, it is fairly
complex, and each bitmap is stage-sized.
In many situations, stage-sized bitmaps will be undesirable (because of the memory
requirements). The benefit is that the class works in all situations (known to me),
whereas the better-known CollisionDetection class by Grant Skinner fails in situations where objects are nested and/or transformed.
package com.kglad{
import flash.display.BitmapData;
import flash.geom.*;
import flash.display.DisplayObject;
import flash.utils.getQualifiedClassName;
import flash.display.Bitmap;
public class HitF{
public static var instance:HitF;
private var dataObj:Object;
public static function getInstance():HitF {
if (instance==null) {
instance = new HitF( new SingletonEnforcer() );
}
return instance;
}
public function HitF( pvt:SingletonEnforcer ) {
dataObj = {};
}
BitmapData
public function hit(dobj1:DisplayObject,dobj2:DisplayObject):Boolean{
if(!dobj1 || !dobj2){
return false;
}
if(!dobj1.stage || !dobj2.stage){
return false;
}
if(!dobj1.hitTestObject(dobj2)){
return false;
}
if(!dataObj[dobj1.name]){
dataObj[dobj1.name] = {};
}
if(!dataObj[dobj2.name]){
dataObj[dobj2.name] = {};
}
dataObj[dobj1.name].currentMat =
dobj1.transform.concatenatedMatrix;
dataObj[dobj2.name].currentMat =
dobj2.transform.concatenatedMatrix;
checkForChangeF(dobj1);
checkForChangeF(dobj2);
//
if(dataObj[dobj1.name]["bmpd"].hitTest(new
Point(0,0),255,dataObj[dobj2.name]["bmpd"],new Point(0,0),255)){
return true;
} else {
return false;
}
}
private function checkForChangeF(dobj:DisplayObject):void{
var createBMPDBool:Boolean = false;
if(getQualifiedClassName(dobj).indexOf("MovieClip")>-1){
if(dataObj[dobj.name].previousFrame!=dataObj[dobj.name].currentFrame){
dataObj[dobj.name].previousFrame =
dataObj[dobj.name].currentFrame;
createBMPDBool = true;
}
}
if (!dataObj[dobj.name]["bmpd"] ||
dataObj[dobj.name].currentMat.tx
!=
dataObj[dobj.name].previousMat.tx
||
dataObj[dobj.name].currentMat.ty
!=
dataObj[dobj.name].previousMat.ty
||
dataObj[dobj.name].currentMat.a
!=
dataObj[dobj.name].previousMat.a
||
53
54
Chapter 4 n What You Should Know
dataObj[dobj.name].currentMat.b
!=
dataObj[dobj.name].previousMat.b
dataObj[dobj.name].currentMat.c
!=
dataObj[dobj.name].previousMat.c
dataObj[dobj.name].currentMat.d != dataObj[dobj.name].previousMat.d) {
||
||
dataObj[dobj.name].previousMat =
dataObj[dobj.name].currentMat;
createBMPDBool = true;
}
if(createBMPDBool){
createBmpdF(dobj);
}
}
private function createBmpdF(dobj:DisplayObject):void{
if(dataObj[dobj.name]["bmpd"]){
dataObj[dobj.name]["bmpd"].dispose();
}
dataObj[dobj.name]["bmpd"] = new
BitmapData(dobj.stage.stageWidth,dobj.stage.stageHeight,true,0x22000000);
dataObj[dobj.name]["bmpd"].draw(dobj,dataObj[dobj.name].currentMat);
}
public function clearF(dobj:DisplayObject):void{
if(dataObj[dobj.name]){
delete dataObj[dobj.name];
}
}
}
}
internal class SingletonEnforcer{}
Conditional Compiling
Conditional compiling allows you to compile different code blocks to your ActionScript depending on the value(s) of certain configuration constants. For example,
you can have blocks of code that are compiled only when you’re publishing for the
web and different blocks of code that are compiled only when you’re publishing for
an Android device in the same class file, along with code that is published for both
situations.
You use CONFIG::xx constants to indicate a code block, and you assign the constants a
Boolean in the Config Constants panel: File > Publish Settings > ActionScript 3.0
Dictionary
Settings > Config Constants (see Figure 11.16 in Chapter 11, “Social Gaming: Social
Networks”).
For an example using web and Android publishing, in the same (or different) class
files you could have:
CONFIG::ANDROID{
// some block of code published only when CONFIG::ANDROID is true
}
CONFIG::WEB{
// some block of code published only when CONFIG::WEB is true
}
You add CONFIG::ANDROID and CONFIG::WEB in the Config Constants panel. By assigning
true or false to each configuration constant and then publishing, you control whether
one, both, or neither code block is included in the compiled code. We’ll discuss this
further in Chapter 11.
Dictionary
In ActionScript, if you think of an associative Array as a generalized indexed Array,
then a Dictionary is a generalized associative Array. Where a standard Array accepts
non-negative integers for keys and an associative Array accepts strings for keys, a
Dictionary accepts objects for keys.
Notice that if you want to store objects as keys, using an associative
you the results you expect.
Array
won’t give
var mc1:MovieClip = new MovieClip();
var mc2:MovieClip = new MovieClip();
var obj:Object = {};
obj[mc1]=“a“;
obj[mc2]=“b“;
// This traces true
trace(obj[mc1]==obj[mc2]);
var d:Dictionary = new Dictionary();
d[mc1] = “a“;
d[mc2] = “b“;
// This traces false
trace(d[mc1]==d[mc2]);
I’ll make use of a static Dictionary in Chapter 7, “Optimizing Game Performance,”
where I use it to store object references for memory tracking.
55
56
Chapter 4 n What You Should Know
DispatchEvent
A DispatchEvent is a very useful way to communicate between two related classes.
When you create a class C instance in class P and something occurs in class C that
you want communicated to P, you can dispatch a custom event in C and listen for
that event in P. For example:
In P:
var c:C = new C();
c.addEventListener(“customE”,customF);
private function customF(e:Event):void{
// something occurred to c, in C
}
In C:
private function f():void{
// something happened in C
dispatchEvent(new Event(“customE“));
}
If
C
is a class that extends any of the EventDispatcher subclasses, you need not use an
statement. Otherwise, at the top of your class file C, use:
import
import flash.events.EventDispatcher;
ExternalInterface
The ExternalInterface class allows communication between ActionScript and the
SWF container. Typically, the container is the embedding HTML page, and the communication is between ActionScript and JavaScript.
Communication from ActionScript to JavaScript is especially easy using the static
call() method of the ExternalInterface class. You just use:
ExternalInterface.call(“some_JavaScript_function”,some_ActionScript_variable);
to pass some_ActionScript_variable to some_JavaScript_function in the embedding
HTML. You can do quite a bit more with the ExternalInterface call() method,
including calling JavaScript functions created with ActionScript. For example:
var jsXML:XML =
<![CDATA[
function(s){
function nestJS_F(sVar){
return sVar+” augmented“;
};
Listeners versus Weak Listeners
return nestedJS_F(s);
}
]]>
var myResult = ExternalInterface.call(jsXML , “String from Flash“);
trace(myResult);
You can also call ActionScript functions from JavaScript using the static
ExternalInterface addCallback() method. This is more complicated to use because
not all HTML code that properly embeds a SWF will allow callbacks. You’ll find
more details about this in Chapter 11, “Social Gaming: Social Networks.”
Garbage Collection
Garbage collection is a process used by Flash to clear memory of unneeded storage.
Flash has a very rigorous method for determining what is unneeded. Chapter 7,
“Optimizing Game Performance,” details this method.
Briefly, if objects are not in the display list and have no references and no listeners,
Flash marks them for removal. In the text that follows, I use the abbreviation gc with
and without various suffixes, such as gc’d and gc’ing, to refer to garbage-collection
activities.
getTimer()
This returns the number of milliseconds the Flash Player has been running. This is
one of the more useful utilities in Flash, especially for testing code.
For example, to test how quickly a block of code executes, you can use:
var startTime:int = getTimer();
/*
code block
*/
trace(getTimer()-startTime);
Listeners versus Weak Listeners
A weak listener is created using an addEventListener() method that doesn’t prevent
gc’ing the object to which it is applied. The default (strong) listener does prevent
gc’ing the listener object. The only difference between the two listeners is that the
weak listener has a fifth parameter in addEventListener() assigned to true.
The first parameter is the event type, and the second parameter is the listener function. The third parameter is a Boolean that determines whether to use the capture phase
of this event. There may be occasions when you find this useful, but I never have.
57
58
Chapter 4 n What You Should Know
Its only purpose would be if you want a parent listener to execute before a child listener or because you want to toggle the child listener function by toggling the event’s
propagation.
In any case, to use the fifth parameter, you have to use the third and fourth parameters. The fourth parameter is an int that indicates the priority of the listener.
Again, there may be occasions when you find this useful, but I never have.
Its only purpose would be if you have more than one listener applied to the same
object and you want to control the order in which their listener functions execute.
Normally, the listener that is added first calls its listener function first. If you need
to control the call order dynamically, use the fourth parameter.
// The usual listener
mc.addEventListener(MouseEvent.CLICK,f);
// The same listener using the default third and fourth parameters and a
// boolean indicating it is a weak listener
mc.addEventListener(MouseEvent.CLICK,f,false,0,true);
But you cannot depend on either behavior (allowing gc’ing when using weak listeners
and preventing gc’ing when using a strong listener) because of differences in how
listeners are handled in different Flash Player versions. We’ll discuss this further in
Chapter 7.
Loops
There are two significantly different loop types in ActionScript: one type that executes from start to end before any other code executes and before anything updates
on-stage, and one type that does neither of those things.
do Loops, for Loops, and while Loops
Loop types that execute from start to end before anything updates on-stage include
do loops, for loops, and while loops. They cannot be used to animate objects because
no matter what code you use to execute the loop and no matter what you do to try to
slow it down, it will still execute from start to finish before anything changes onstage. They are appropriately used for rapid execution of code.
However, there are situations when you might want to break these loops into more
than one chunk so the stage can update between each chunk.
Chunks
For example, if you have a for loop that takes 10 seconds to execute, your game will
appear to freeze for 10 seconds after this loop starts. Nothing will update on-stage,
Loops
and nothing will respond to user input. Either you should warn your user before
starting that loop or you should break that loop into several smaller chunks that
allow visual updates to users so they don’t think your game is broken.
For example, this for loop that adds odd numbers (and shows the first
bers sum to m*m) freezes my Flash Player for about 9 seconds.
var
var
var
var
var
var
for
m
odd num-
for
loop) into
i:Number;
n:Number=3000000000;
s:Number=0;
startI:Number=1;
endI:Number=n
startTime:int=getTimer();
(i=startI; i<endI; i+=2) {
s+=i;
}
// 9 seconds
trace((getTimer()-startTime)/1000,s,n*n/4,s-n*n/4);
The following technique shows how to break this (and any other
chunks that allow the Flash Player to update every second.
var i:Number;
var n:Number=3000000000;
var s:Number=0;
var startTime:int=getTimer();
// This is the number of chunks into which the previous for loop will
// be broken. If the previous for loop took about 9 seconds, using 10
// chunks means there will be updates about every 0.9 seconds.
var chunks:int=10;
var startI:Number=1;
var endI:Number=n/chunks;
var t:Timer=new Timer(100,1);
t.addEventListener(TimerEvent.TIMER,f);
f();
function f(e:Event=null):void {
for (i=startI; i<endI; i+=2) {
s+=i;
}
trace(“stage update”,startI,endI,s);
if (endI<n) {
t.reset();
t.start();
} else {
trace((getTimer()-startTime)/1000,s,n*n/4,s-n*n/4);
59
60
Chapter 4 n What You Should Know
}
startI+=n/chunks;
endI+=n/chunks;
}
break
There may be occasions when you’ll want to terminate one of these loops when a
condition is met. For these situations, you can use the break statement.
for(var i:int=0;i<1000;i++){
if(i>10){
break;
}
trace(i);
}
If you want to terminate a nested for loop, name your loop and use that name in the
break statement. For example, compare:
for(var i:int=0;i<5;i++){
for(var j:int=0;j<5;j++){
if(i==2 && j==3){
break;
}
trace(i,j);
}
}
to:
outsideLoop:
for(var i:int=0;i<5;i++){
for(var j:int=0;j<5;j++){
if(i==2 && j==3){
break outsideLoop;
}
trace(i,j);
}
}
I will discuss
do
loops,
for
loops, and
while
loops again in Chapter 7.
enterFrame, setInterval(), and Timer Loops
loops, setInterval() loops, and Timer loops make up the second loop type.
These are appropriate for animating on-stage objects using ActionScript.
enterFrame
Math Class
The Flash Player attempts to maintain a frequency of 1/stage.frameRate for the
enterFrame loop. How successful the Flash Player is in maintaining that frequency
depends on a number of factors, which I’ll explain in detail in Chapter 7.
An important point to remember about these loops is that, at best, the average loop
frequency will be accurate to within a few percent of the frequency the loop is
intended to execute. However, the timing between consecutive loops can and will
vary widely. At best, consecutive loops will execute at between 50 percent and
150 percent of the intended frequency.
I recommend avoiding setInterval() (because it’s easy to misuse), and it’s no
more accurate than the other loops. If you’re tempted to use it, at least execute
clearInterval() before every setInterval().
var whateverI:int;
clearInterval(whateverI);
whateverI = setInterval(f,100);
function f():void{
}
Math Class
This section covers facts and topics in geometry, linear interpolation, the modulo
operator, randomizing and trigonometry. These topics occur repeatedly in many
types of games. Becoming familiar with these topics and knowing the listed facts
will help you create games.
Geometry
Let’s start with four basic assumptions from plane geometry.
n
A straight angle is 180 degrees (see Figure 4.1).
Figure 4.1
a = 180.
Source: © 2013 Keith Gladstien, All Rights Reserved.
n
The sum of a triangle’s angles is 180 degrees (see Figure 4.2).
Figure 4.2
a + b + c = 180.
Source: © 2013 Keith Gladstien, All Rights Reserved.
61
62
Chapter 4 n What You Should Know
n
Vertical angles are equal (see Figure 4.3). Vertical angles are two nonadjacent
angles formed by two intersecting lines.
Figure 4.3
a = c.
Source: © 2013 Keith Gladstien, All Rights Reserved.
n
Adjacent angles formed by two intersecting lines sum to 180 degrees (see Figure 4.4).
Adjacent angles are neighboring angles (i.e., they share a side and vertex formed by
two intersecting lines.
Figure 4.4
b + c = 180.
Source: © 2013 Keith Gladstien, All Rights Reserved.
Angles of Reflection
These four plane geometry facts are basic. But, armed with those facts and using
logic, you can solve several practical problems encountered in game programming.
For example, in a game in which objects bounce off walls, you can determine the
new angle of motion.
More precisely, given an object at A moving along a straight line at angle b toward a
vertical wall at B that reflects (or bounces) off the wall toward C (like a billiard ball),
what angle should the object move along after reflection? (See Figure 4.5.)
Math Class
Figure 4.5
First, where is angle b and where is the angle of reflection? (See Figure 4.6.)
Source: © 2013 Keith Gladstien, All Rights Reserved.
Figure 4.6
Angle b and angle of reflection r.
Source: © 2013 Keith Gladstien, All Rights Reserved.
What is angle r? Because:
b + 90 + c = 180
c = 90 − b
and
r = c + 90
r = 90 − b + 90 = 180 − b
See Figure 4.7.
63
64
Chapter 4 n What You Should Know
Figure 4.7
r = c + 90 and c = 90 − b; therefore r = 180 − b.
Source: © 2013 Keith Gladstien, All Rights Reserved.
This argument is valid if b is between 0 and 90 degrees. Similar arguments show this
statement is true for all angles of b.
Using a similar (or simpler) argument, you should be able to show that an object
moving along a straight line at angle b that reflects off of a horizontal wall moves
along a line at angle 360 − b after reflection.
Pythagorean Theorem
In a right triangle, which is a triangle with a 90-degree angle, the sum of the sides’
lengths squared is equal to the hypotenuse’s length squared (see Figure 4.8).
Figure 4.8
C*C=A*A+B*B
Source: © 2013 Keith Gladstien, All Rights Reserved.
Most commonly, this is used in Flash games when calculating the distance
between two on-stage objects. For example, determining the distance between the
registration points of mc1 and mc2 is a direct application of the Pythagorean theorem.
See Figure 4.9.
var distance:Number = Math.sqrt((mc1.x-mc2.x)*(mc1.x-mc2.x)+(mc1.y-mc2.y) *
(mc1.y-mc2.y));
Math Class
Figure 4.9
If the leftmost green dot is mc1" and the other is mc2", then C = distance between mc1" and mc2", A =
mc2.x" − mc1.x", and B = mc2.y − mc1.y.
Source: © 2013 Keith Gladstien, All Rights Reserved.
Linear Interpolation
It’s surprising how many situations lend themselves to linear interpolation. Briefly
stated, any time you have two pairs of related numbers, you can define a linear relationship between the two pairs and extend the relationship to any number of pairs.
For example, if you have a masked vertical DisplayObject (for example, mc) that you
want to scroll up and down using a horizontal scrollbar (for example, sb), and all
your objects have top-left registration, you can use:
// Align mc’s top with mask_mc’s top
mc.y=mask_mc.y;
mc.mask=mask_mc;
// Create the rectangle to define the region where the scrollbar’s
// thumb will drag
var rect:Rectangle = new Rectangle(0,0,sb.width-sb.thumb.width,0);
// Assign listeners
sb.thumb.addEventListener(MouseEvent.MOUSE_DOWN,downF);
sb.thumb.addEventListener(MouseEvent.MOUSE_UP,upF);
// paramF() is the function that defines the parameters used for scrolling.
// We want sb.thumb’s x position to control mc’s y parameter. We already
// know two points in this relationship.
// The first is (0,mask_mc.y) when the thumb is to the far left (0), mc’s
// top should align with mask_mc’s top (mc.y=mask_mc.y) and the second is
// (sb.width-sb.thumb.width,mask_mc.y-mc.height+mask_mc.height) when
// the thumb’s right side is to the far right of the scrollbar (sb.width// sb.thumb.width)), the mc’s bottom should align with mask_mc’s bottom.
// We can now use linear interpolation to find the parameters to be used
// to associate all the other x-values that sb.thumb can take with the
// corresponding y-values of mc.
paramF(sb.thumb,0,mask_mc.y,sb.width-sb.thumb.width,mask_mc.y-mc.height+mask_mc.
height);
65
66
Chapter 4 n What You Should Know
function downF(e:MouseEvent):void{
sb.thumb.startDrag(false,rect);
sb.thumb.addEventListener(Event.ENTER_FRAME,scrollF);
}
function upF(e:MouseEvent):void{
sb.thumb.stopDrag();
sb.thumb.removeEventListener(Event.ENTER_FRAME,scrollF);
}
function scrollF(e:Event):void{
// Here is where the parameters found in paramF() are used to
// assign mc’s y position
mc.y = sb.thumb.m*sb.thumb.x+sb.thumb.b;
}
function paramF(mc:MovieClip,x1:Number,y1:Number,x2:Number,y2:Number):void{
mc.m = (y1-y2)/(x1-x2);
mc.b = y1 - mc.m*x1;
}
You can use exactly the same code (with two lines of code changed) in many different situations. The two lines are:
paramF(sb.thumb,0,mask_mc.y,sb.width-sb.thumb.width,mask_mc.ymc.height+mask_mc.height);
and
mc.y = sb.thumb.m*sb.thumb.x+sb.thumb.b;
You aren’t limited to x and y properties. With few changes, you can use the same
code any time you want a change in any property in one object to effect a change
in some property in another object.
In fact, you aren’t limited to associating properties. For example, you can control the
timeline play of a MovieClip using a scrollbar (in other words, scroll a MovieClip timeline), and you can use a MovieClip’s timeline play to control an object property.
Specifically, if you want to scroll a MovieClip (for example,
cal scrollbar (for example, sb), use:
mc)
timeline using a verti-
paramF(sb.thumb,sb.thumb.height,1,sb.height,mc.totalFrames);
and in an
Event.ENTER_FRAME
listener function, use:
mc.gotoAndStop(int(sb.m*sb.y+sb.b));
If you want the
mc’s
timeline to control vertical
sb’s
thumb, use:
paramF(mc,1,sb.thumb.height,mc.totalFrames,sb.height);
Math Class
and in an
Event.ENTER_FRAME
listener function, use:
sb.thumb.y = mc.m*mc.currentFrame+mc.b;
Modulo Operator
The modulo operator (%) is very handy in several situations. You can use it in an if
statement to filter certain values of the iteration variable. For example, if n is even
and you want to add the first n/2 odd numbers and the first n/2 even numbers, you
can use one for loop and the modulo operator.
var i:Number;
var n:Number=500000;
var sOdd:Number=0;
var sEven:Number=0;
for(i=1;i<=n;i++){
// This checks for odd numbers
if(i%2==1){
sOdd+=i;
} else {
sEven+=i;
}
}
// confirm the odd numbers sum to n*n/4 and the even numbers sum to n*(n+2)/4
trace(sOdd,”=”,n*n/4,”||”,sEven,”=”,n*(n+2)/4);
The modulo operator is also useful in tiling situations. For example, if you want to
tile the stage, the following code shows how you can use the modulo operator (see
Figure 4.10).
var
var
var
var
var
for
}
len:int=12;
gap:int = 2;
rows:int=Math.floor(stage.stageHeight/(len+gap));
cols:int=Math.floor(stage.stageWidth/(len+gap));
sp:Sprite;
(var i:int=0; i<rows*cols; i++) {
sp = new Sprite();
addChild(sp)
with (sp.graphics) {
beginFill(0xffffff*Math.random());
drawRect(0,0,len,len);
endFill();
}
sp.x = (i%cols)*(gap+sp.width);
sp.y = Math.floor(i/cols)*(gap+sp.height)
67
68
Chapter 4 n What You Should Know
Figure 4.10
Tiling any rectangular region with any tile shape is easy using the modulo operator.
Source: Adobe Systems Incorporated.
Randomizing
It’s a little late to be explaining randomization, because I’ve already used it at least
twice before this subsection. But, better late than never.
Randomizing is such a key part of game creation that I doubt I’ve ever made a game
that doesn’t use Math.random().
Math.random() returns a pseudo (or almost) random number greater than or equal to
zero and less than one. Using Math.random(), you can return a random number
between any two numbers.
function randomF(n1:Number,n2:Number):Number{
if(n1<n2){
return n1+(n2-n1)*Math.random();
} else {
return n2+(n1-n2)*Math.random();
}
}
For example, if you want to generate a random number between 2.2 and 2.3, you
would use:
var r1:Number = randomF(2.2,2.3);
Math Class
If you wanted to return a random integer between any two integers, you could use:
function randomF(n1:Number,n2:Number):int {
// Error check to ensure two integers were passed.
if(n1!=int(n1) || n2!=int(n2)){
return int.MIN_VALUE;
}
if (n1<n2) {
return int(n1+(1+n2-n1)*Math.random());
} else {
return int(n2+(1+n1-n2)*Math.random());
}
}
In the modulo operator example, I used
Math.random()
to generate a random color:
var randomColor:uint = 0xffffff*Math.random();
To position an object (for example, mc) with an upper-left registration point at a random on-stage location, you can use:
mc.x = (stage.stageWidth-mc.width)*Math.random();
mc.y = (stage.stageHeight-mc.height)*Math.random();
To go to a random frame in a
MovieClip
(for example,
mc)
timeline, you can use:
mc.gotoAndStop(Math.ceil(Math.random()*mc.totalFrames));
Because Math.random() generates a number greater than or equal to zero and less than
one, rounding up is appropriate. Math.ceil() should be used because it rounds up.
Trigonometry
You only need to know three things about trigonometry for Flash games: the sine of
an angle, the cosine of an angle, and arctangent of a number.
The sine and cosine are functions of an angle, and the arctangent is a function of a
number. The sine and cosine relate angles with ratios of right triangle sides, while the
arctangent relates the ratio of right triangle sides to angles.
First, if you’re already familiar with trigonometry, you need to remember that zero
degrees is east, not north, and positive angles in Flash are measured in the clockwise
direction, just the opposite of what you learned studying mathematics. This actually
makes sense because the positive y-axis in Flash is down, the opposite of what you
learned in math class.
69
70
Chapter 4 n What You Should Know
The following are definitions.
n
In a right triangle, the sine of an angle is the ratio of the side opposite the angle
and the hypotenuse.
n
In a right triangle, the cosine of an angle is the ratio of the side adjacent to the
angle and the hypotenuse.
n
In a right triangle, the arctangent of the ratio of opposite and adjacent sides to
an angle b is b. (See Figure 4.11.)
Figure 4.11
Sine of b = B/C, cosine of b = A/C, sine of a = A/C, cosine of a = B/C, arctangent of B/A = b.
Source: © 2013 Keith Gladstien, All Rights Reserved.
Notice that the absolute size of the triangle doesn’t matter. The lengths of the sides of
the triangle in Figure 4.11 could be several angstroms or several light years, and they
could shrink or expand. As long as the angles stay the same, the ratios of the sides
will be the same.
In ActionScript, the sine function is Math.sin(), and the cosine function is Math.cos().
The arctangent function is Math.atan(), which accepts the ratio of side length. Flash
also has a convenient Math.atan2() function that accepts the lengths of the two
(opposite and adjacent) sides instead of the ratio of those two sides like Math.atan().
and Math.cos() accept angles in radians, and Math.atan() and Math.atan2()
return angles in radians. If you prefer to work in degrees, you can convert from
radians to degrees by multiplying radians by 180/Math.PI and you can convert from
degrees to radians by multiplying by Math.PI/180.
Math.sin()
So, how would you use these functions? Check Figure 4.11 and imagine you have an
object at the vertex of angle b whose movement you want to animate to the vertex of
angle a. To do this, you need to update your object’s x and y properties repeatedly
using a loop (such as enterFrame) and using Math.sin() and Math.cos().
Math Class
// The speed of movement
var speed:int=3;
// The x,y of vertex b
var vertex_bX:int = 50;
var vertex_bY:int = 50;
// The x,y of vertex c
var vertex_cX:int = 450;
var vertex_cY:int = 250;
// Calculate the angle between the two vertices b and c
var b = Math.atan2(vertex_cY-vertex_bY,vertex_cX-vertex_bX);
// Create an object
var sp:Sprite = new Sprite();
with (sp.graphics){
beginFill(0x990000);
drawCircle(0,0,10);
endFill();
}
addChild(sp);
// Start sp at vertex b
sp.x = vertex_bX;
sp.y = vertex_bY;
// Create a loop to animate (move) sp from vertex b to vertex c.
this.addEventListener(Event.ENTER_FRAME,enterFrameF);
function enterFrameF(e:Event):void{
// Update sp’s x and y properties
sp.x += int(speed*Math.cos(b));
sp.y += int(speed*Math.sin(b));
// Stop sp’s movement when it reaches vertex c
if(sp.x >= vertex_cX){
this.removeEventListener(Event.ENTER_FRAME,enterFrameF);
}
}
All linear movement reduces to using the same ideas and code.
1. You generally have two points.
2. You calculate the angle between the two points using
Math.atan2().
3. You start a loop like
Math.atan()
or
Event.ENTER_FRAME.
4. You update your object’s
from 2.
x
property using
Math.cos()
of the calculated angle
71
72
Chapter 4 n What You Should Know
5. You update your object’s
from 2.
y
property using
6. Check for an end loop and/or
hitTest()
Math.sin()
of the calculated angle
and/or boundary condition.
7. Take appropriate action given a positive test in 6.
Here’s an example using an object bouncing off the stage boundaries.
// Create an object
var mc:MovieClip = new MovieClip();
with (mc.graphics){
beginFill(0x990000);
drawCircle(0,0,10);
endFill();
}
addChild(mc);
mc.cacheAsBitmap
// The speed of movement
var speed:int=8;
initializeF(mc);
function initializeF(mc:MovieClip):void{
// Assign initial position
mc.x = mc.width/2+Math.random()*(stage.stageWidth-mc.width/2);
mc.y = mc.height/2+Math.random()*(stage.stageHeight-mc.height/2);
// Assign initial angle
var angle:Number = 2*Math.PI*Math.random();
// Instead of repeatedly calculating the cosine and sine of the same
// angle, do it once, and store the values in variables. This is much
// faster than repeatedly calculating the same value. Even values like
// the stage dimensions and object dimensions can be more efficiently
// stored in variables. This will be discussed further in Chapter 7.
mc.xVector = 1+int(speed*Math.cos(angle));
mc.yVector = 1+int(speed*Math.sin(angle));
}
this.addEventListener(Event.ENTER_FRAME,enterFrameF);
function enterFrameF(e:Event):void{
// Update mc’s x and y properties
mc.x += mc.xVector;
mc.y += mc.yVector;
// Bounce off stage boundaries.
if(mc.x >= stage.stageWidth-mc.width/2 || mc.x <= mc.width/2){
// This follows because cos(180-b) = -cos(b) and
// sin(180-b) = sin(b), the proof of which is beyond the
//scope of this book, but Figure 4.12 may suggest the
Math Class
// validity of this assertion.
mc.xVector *= -1;
}
if(mc.y >= stage.stageHeight-mc.height/2 || mc.y <= mc.height/2){
// This follows because cos(360-b) = cos(b) and
// sin(360-b) = -sin(b), and again the proof of this is
//beyond the scope of this book, but Figure 4.13 may
//suggest the validity of this assertion.
mc.yVector *= -1;
}
}
Figure 4.12
Figure suggesting cos(180−b) = cos(b’) = −A/C = −cos(b) and sin(180−b) = sin(b’) = B/C = sin(b).
Source: © 2013 Keith Gladstien, All Rights Reserved.
Figure 4.13
Because 360−b = −b, figure suggesting cos(−b) = A/C = cos(b) and sin(−b) = −B/C = −sin(b).
Source: © 2013 Keith Gladstien, All Rights Reserved.
By the way, the Figures 4.12 and 4.13 illustrate how to convert from polar to rectangular coordinates. That is, given b and C, find A and B:
A = C*Math.cos(b);
B = C*Math.sin(b);
73
74
Chapter 4 n What You Should Know
They also illustrate how to convert from rectangular to polar coordinates. That is,
given A and B find C and b:
C = Math.sqrt(A*A+B*B);
b = Math.atan2(B,A);
mouseOut versus rollOut
The MouseEvent.MOUSE_OUT (= mouseOut) is dispatched when you mouse off of the
object to which the listener is applied, even if you’re still over a child of the object.
The MouseEvent.ROLL_OUT (= rollOut) is dispatched only when the mouse moves off
the object and all its children.
To see the difference, create a MovieClip that contains two shapes that overlap. Convert one of the shapes to a child MovieClip. Add the parent MovieClip to the stage and
assign an instance name—for example, mc. Then use the following code to see how
the two mouse events differ.
mc.addEventListener(MouseEvent.MOUSE_OVER,f);
mc.addEventListener(MouseEvent.MOUSE_OUT,f);
mc.addEventListener(MouseEvent.ROLL_OVER,f);
mc.addEventListener(MouseEvent.ROLL_OUT,f);
function f(e:MouseEvent):void{
trace(e);
}
Pausing/Restarting Your Game
You can use the
frameRate
property of the stage to pause and restart your game:
var originalFR:int = stage.frameRate;
pausePlayToggle.addEventListener(MouseEvent.CLICK,pausePlayF);
function pausePlayF(e:MouseEvent):void{
// After setting to zero, the frameRate is actually reported as .01
if(stage.frameRate>.01){
stage.frameRate = 0
} else {
stage.frameRate = 24;
}
}
Registration Points
All display objects have a registration point, which is used by Flash to position,
rotate, and transform the object (when using ActionScript). When creating objects
Registration Points
in the Flash IDE (integrated development environment), the registration point is
denoted by the plus sign (+) when editing the symbol. For objects created with
ActionScript, 0,0 is the object’s registration point.
When using the drawing methods of an object’s graphics property, x and y parameters are required, and these are offsets from the object’s registration point. All display object positions, rotations, and transforms, when changed using ActionScript,
are relative to the object’s registration point. In the IDE, transforms are relative to
the transform point, which, by default, is the object’s center (but can be changed
using the Transform tool).
Here’s an example of how an object’s registration point and the drawing methods
impact what is seen on-stage.
var sp:Sprite = new Sprite();
var xVar:int = 40;
var yVar:int = 20;
with(sp.graphics){
beginFill(0xaa0000)
drawCircle(xVar,yVar,15);
endFill()
}
addChild(sp);
this.addEventListener(Event.ENTER_FRAME,f);
function f(e:Event):void{
sp.rotation+=3;
}
Change the values of xVar and yVar until you understand how sp’s registration point
of 0,0 and drawCircle()’s first two parameters control what you see on-stage.
With ActionScript 3.0, you can change the registration point of any display object
using the following changeRegPt() function. That function accepts three parameters:
the object reference, the new x registration point, and the new y registration point.
To most easily see the result of changing an object’s registration point, just like in the
previous example, use a loop that rotates your object.
// Change mc’s registration point to its top left.
// changeRegPt(mc,0,0);
// Change mc’s registration point to its center
// changeRegPt(mc,mc.width/2,mc.height/2);
// Change mc’s registration point to its bottom right
changeRegPt(mc,mc.width,mc.height);
function changeRegPt(dobj:DisplayObjectContainer,x:Number,y:Number){
var r:Rectangle = dobj.getBounds(dobj);
75
76
Chapter 4 n What You Should Know
for(var i:int=0;i<dobj.numChildren;i++){
dobj.getChildAt(i).x -= r.x+x;
dobj.getChildAt(i).y -= r.y+y;
}
dobj.x += r.x+x;
dobj.y += r.y+y;
}
// The code below is not needed to change an object’s registration point.
// It is used to demonstrate the registration point has been changed.
this.addEventListener(Event.ENTER_FRAME,rotateF);
function rotateF(e:Event):void{
mc.rotation+=3;
}
SharedObject
The Flash SharedObject acts like a browser cookie. You can store variables and values
in a SharedObject, which is then stored on the user’s computer. Using this, you can
store, for example, a game state so that when a user restarts your game, he or she can
restart from the last saved game state.
The SharedObject uses the static getLocal() method to create and retrieve an object
saved on the user’s computer. You create a reference using:
var so:SharedObject = SharedObject.getLocal(“your_game_so“);
if(so.data.level){
trace(“this user’s last completed level was “+so.data.level);
// start game from so.data.level
} else {
// start game from level 1
}
// After each level is completed, update so.data.level. For example,
// when level 1 is completed
so.data.level = 2;
// save your data update to the user’s hard drive by using the flush() method
so.flush();
Tweening with ActionScript
The Flash Tween class leaves a lot to be desired for several reasons. The main drawback is that you need to instantiate instances of the Tween class every time you want
to tween something, and you need to instantiate different Tween instances for each
property you want to tween.
Tweening with ActionScript
Not only does that add housekeeping work for the Flash Player, but it also exposes a
difficult-to-debug issue that occurs when you declare and instantiate a Tween instance
inside a function. The Tween instance is local to the function and may be gc’d before
the Tween completes terminating your object’s Tween.
I recommend you avoid it and use one of the third-party tween classes. There are
several good ones, but my preference is TweenLite.
You can download GreenSock’s TweenLite at www.greensock.com/tweenlite. There
are other GreenSock tweening classes that have more (and one with fewer) capabilities, but I’ve never needed anything more (or less) than TweenLite. There is documentation in the TweenLite.as file and easier to read documentation at www.
greensock.com/as/docs/tween.
To get started after downloading and extracting everything in the zip file, you can
drag the com/greensock directory anywhere and use it in any project. Just import
com.greensock.TweenLite and any easing class (like Back, Bounce, etc.) needed, and
use the static TweenLite.to() and/or TweenLite.from().
Notice that you don’t create instances of GreenSock’s TweenLite. You use one of two
static methods. That avoids the main drawback of the Flash Tween class.
For example, to tween an object
to 333, use:
mc’s x
property for
2
seconds from its current value
import com.greensock.TweenLite;
TweenLite.to(mc,2,{x:333});
// To tween for 1 second from 333 to mc’s current y, use
TweenLite.from(mc,1,{y:333});
// To delay a tween for 4 seconds, use the delay parameter
TweenLite.to(mc,1,{alpha:.2,delay:4 });
// To call a function completeF() when the tween completes, use
TweenLite.to(mc,1,{rotation:12,onComplete:completeF});
You can also use other parameters, such as delay, useFrames, easeParams, immediateRender,
onInit, onInitParams, onStart, onStartParams, onUpdate, onUpdateParams, onComplete,
onCompleteParams, onReverseComplete, onReverseCompleteParams, overwrite, and paused.
All are explained in the help files.
I have rarely used most of those. I use the delay, onComplete, and onCompleteParams
parameters frequently. Occasionally, I use onUpdate, onUpdateParams, and overwrite.
But I’ve rarely, if ever, used the rest. So, there isn’t much to learn to master the use
of TweenLite in almost all situations.
77
78
Chapter 4 n What You Should Know
Variable Names
You can name most objects whatever you choose, but you do have to follow some
rules.
First, ActionScript is case sensitive. So,
myVar
isn’t the same as
myvar
or
MyVar.
Second, use variable names that make sense and help your code be understandable.
And, just because Flash allows you to use names such as:
var Sprite:MovieClip = new MovieClip();
var int:Array = [];
var Class:Sound = new Sound();
doesn’t mean you should.
Third, you cannot use any non-alphanumeric characters other than the underscore
(_) and dollar sign ($) in variable names. For example, you cannot use the dash:
var mc1-mc2:MovieClip = new MovieClip();
var mc1.mc2:MovieClip = new MovieClip();
Fourth, you cannot start variable names with anything other than a letter, a dollar
sign, or an underscore. That is, you cannot start a variable name with a number.
So, the following will trigger an error:
var 1var:MovieClip = new MovieClip();
Finally, there are some (key) words Flash absolutely will not let you use in any manner other than the one intended. For example, you cannot redefine the following key
words:
as, break, class, for, if, in, internal, is, package, private, protected, public
switch, var, while
The same holds true for all class properties, such as x and y. You will see an error
1084: Syntax Error: Expecting Identifier before xxx, if xxx is a key word. If xxx is a
class property, you will see an error 1152: A Conflict Exists with Inherited Definition
some.package.zzz in Namespace Public or 1151: A Conflict Exists with Definition xxx
in Namespace Internal.
Vector
In ActionScript, a Vector class object is an Array that contains a single class (base)
type, and that base type is declared when the vector is defined. For example:
var v:Vector.<Sprite> = new Vector.<Sprite>();
Vector
where the Vector’s base type is Sprite. Strictly speaking, this Vector should contain
only Sprite instances. But any data type that can be cast as a Sprite (for example,
MovieClip) could be added to v.
The only advantage of a Vector over an Array is that accessing Vector elements is
faster than accessing Array elements. In most scenarios that benefit isn’t significant.
But, when you’re accessing large numbers of elements or accessing the elements
many times, you may see a significant benefit. I’ll cover this benefit in more detail
in Chapter 7.
79
This page intentionally left blank
Chapter 5
Using the Flash API
and Starting a Flash Game
This chapter discusses how to use the Flash Application Programming Interface
(API). In the context of OOP languages, an API is a list of all the classes available
for your use, as well as properties, methods, and events available to the class and class
members.
Learning how to use APIs is key to learning and using everything available in not
only Flash, but also third-party libraries such as Facebook applications, Google Web
Services, 3D engines, physics engines, and many more.
In fact, to use libraries as diverse as Away3D, Google Web Services, and Facebook,
the two main things you need are the information in this chapter on how to use
APIs and a quick-start guide or a sample file showing how to initialize each library.
That won’t cover everything you need to know, but it will cover most of what you
need. You’ll still need experience and/or help to do everything possible with each
API, but I’ll cover how to get that extra help, too.
The only way I know to gain experience is to keep trying. Don’t quit. If you can
think logically and you keep trying and keep learning, you’ll gain the experience
you need to be an expert.
Step 1
Create a new FLA, save it in a new directory, assign a document class name Main, and
open Main.as in Flash.
81
82
Chapter 5 n Using the Flash API and Starting a Flash Game
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
}
}
}
You’re ready to start working with the one API everyone using Flash should know
how to use: the Flash API. Click Help > Flash Help > ActionScript > ActionScript
3.0 Reference > Show Packages and Classes List. Bookmark that page for easy access.
(See Figure 5.1.)
Figure 5.1
The initial page of the ActionScript 3.0 API.
Source: Adobe Systems Incorporated.
At the time of this writing, help.adobe.com/en_US/FlashPlatform/reference/actionscript/
3/index.html is the correct link. But that may change, so if it doesn’t work, follow the
steps that start with clicking Help in your Flash Pro program.
Figure 5.1 shows the ActionScript 3.0 API. At the top left are all the Flash packages
(directories, folders), which are collections of the Flash class files. For example, the
Flash.display package contains all the DisplayObject classes, and the Flash.text
Step 1
package contains all the non-TLF text-related classes. Below the Flash packages is a
list of all Flash classes (see Figure 5.2).
List of
classes
organized
by package
name
List of
classes
organized
by class
name
Figure 5.2
If you don’t see the Classes and Packages panels, click Show Packages and Classes List.
Source: Adobe Systems Incorporated.
Both the upper and lower panels on the left lead to the same set of classes. The two
panels are just different ways to present the same class listing. At the top, the classes
are organized by packages with the packages listed alphabetically. At the bottom, the
classes are organized in alphabetical order.
I always find the lower (class-based) panel listing to be the quickest way to find a
particular class. Knowing which class to search isn’t always obvious, but when you
do know which class you want to check, you can quickly see all the available properties, methods, and events and determine whether that class will meet your needs. In
addition, there is often sample code that shows how to use some of the class properties, methods, and/or events.
At the top left of the page, in the Package and Class Filters section, there is a Runtimes link and a Products link. Mouse over each if you want to filter the class list by
a particular Flash Player version and Adobe product (for example, Flex versus Flash).
But even with the most restrictive filtering, the list of classes is overwhelming. Don’t
worry about the list’s length. You don’t need to be familiar with all the classes
because you’ll probably use only a small subset of them in any game.
83
84
Chapter 5 n Using the Flash API and Starting a Flash Game
Because the Main class you created extends the MovieClip class, start with the
MovieClip class. Scroll to MovieClip in the Classes panel and click that link. First,
notice that at the very top, the MovieClip package is listed (see Figure 5.3).
Figure 5.3
There’s a lot of useful information at the top of each class, before you even get to the core of each class
listing—the list of properties, methods, and events available to that class.
Source: Adobe Systems Incorporated.
You’ll use this information whenever you see a 1046: Type Was Not Found or Was
Not a Compile-Time Constant: xxxx compile-time error. You’ll look up the xxxx
class, check the package, and use an import statement to remedy the error. If
xxxx=MovieClip, that would be:
Import flash.Display.MovieClip;
Leave the Adobe help file open to the MovieClip class and go back to your FLA. Get
ready to unleash your inner artist, because we’re finally going to start making a game.
Step 1
First, a word of warning about what follows. We’re going to create a basic game with
one player tank (that you control) that tries to shoot one computer-controlled enemy
tank. This will be a top-down tank combat game. I’ll go through the steps as if
I haven’t made this game yet (which is true), with missteps and all.
I’m presenting it this way to show you how games and coding evolve and, most of all,
how to solve problems you’ll encounter in real-world coding. Some of the errors and
missteps I make are intentional—they’re things that I would’ve done or mistakes I’ve
seen others make. Some of the errors are the result of me coding like I usually do—
and making mistakes like I usually do.
I don’t want to waste your time, so for the next few pages, you should probably just
read and not spend much (or any) time working on creating these class files unless
you find something interesting and you want to test it.
Start by drawing one tank. Don’t waste time making it fancy; just create a new
MovieClip. Choose Insert > New Symbol and assign it a name (for example, _player
tank mc). Select MovieClip from the Type combo box and check Export for ActionScript. In the Class field, type PlayerTank and then click OK.
Notice that Flash will fill the Base Class field with flash.display.MovieClip. This indicates that PlayerTank extends the MovieClip class. The PlayerTank class (that Flash created for you) can now use all the properties, methods, and events of the MovieClip
class. Check the MovieClip API to see what’s available. You don’t need to learn how
to use all the properties, methods, and events, but you should glance over the list so
you have an idea of what you can do with the MovieClip class.
You should be in Symbol editing mode for _player tank mc. Add graphics to _player
tank mc using the Flash drawing tools. A small 30 × 40 rectangle will work for now,
or you can make it a little fancier. But keep its size about 30 × 40 pixels.
Almost all of your objects should have the same registration point so you can consistently position your objects without wasting time checking each one’s registration
point. I prefer using the 0,0 registration point for all MovieClips except those I know
I’ll want to rotate around their center, like this tank. So, center the tank relative to its
registration point. That is, half the tank should be to the left of 0 and half should be
above 0.
Right-click _player tank mc in your Library panel and click Edit Class. You should see
(or you’ll need to create) the following code.
85
86
Chapter 5 n Using the Flash API and Starting a Flash Game
PlayerTank.as
package {
import flash.display.MovieClip;
// Flash knows PlayerTank must extend MovieClip. We can use
// all the properties, methods and events available to the
// MovieClip class in our PlayerTank class.
public class PlayerTank extends MovieClip {
public function PlayerTank() {
// constructor code
// this constructor function executes whenever
// a _player tank mc symbol is dragged from
// the library to the stage and whenever
// code like: var pt:PlayerTank = new PlayerTank();
// executes in another scope. But you should not use the
// new constructor for PlayerTank here or you will probably crash
// Flash. Think about what kind of loop that would create.
}
}
}
Save this
PlayerTank
class file as PlayerTank.as.
Follow the same steps to create an _enemy tank mc with class EnemyTank but make that
MovieClip a different color or do something else so you can distinguish the two
MovieClips. The EnemyTank class code should look exactly like the PlayerTank class
code (at this point), except Player is replaced by Enemy in both the code and the file
name.
The user (you) will control the player tank using the keyboard. The idea I want to
convey is how a game evolves from an idea to code and how the code is almost
never written by starting and finishing one class, then starting and finishing another
class, and so on until you’ve coded the last class.
Typically, you will create a few classes to start, and each will contain almost no
code except some trace() functions for testing. Then, you’ll gradually add code to
one class, and then another class, and so on. Despite adding code to various classes
one after the other, you probably won’t finish coding in each class at about the same
time. You might complete some classes long before others.
The important point here is that there are neither rules nor best practices about how
your game should evolve or how much coding you should do in one class before
adding/editing code in another class. There are guidelines for what should be in each
class, and we’ll cover one of those later, after seeing why that guideline is helpful. The
lack of rules or best practices may seem unsettling, but it also offers flexibility.
Version 0a
First, we’ll make a player tank instance and an enemy instance in Main and then test
the initial version of this game. We won’t even number these first starts because they
show more about what you shouldn’t do than about what you should do.
Version 0a
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
// create a PlayerTank instance using the “new” constructor
var player:PlayerTank = new PlayerTank();
// add player to the display list
addChild(player);
// create an EnemyTank instance
var enemy:EnemyTank = new EnemyTank();
// add enemy to the display list
addChild(enemy);
}
}
}
If you’re unfamiliar with ActionScript, you can find information about what I just
coded using the Flash API and looking at the MovieClip class. The only code I used
was the new constuctor and addChild(). Because Main extends the MovieClip class, it
can use the addChild() method of the MovieClip class.
Remember, if you see an error message that you don’t understand or know how to
resolve, check Appendix A, “Errors That Trigger an Error Message,” after checking
the Flash appendixes. I don’t know about you, but I triggered two errors already
because I forgot to save the EnemyTank class file.
Remember, making the class isn’t enough. You have to save it for the code to be
compiled into a SWF. Changes made to an unsaved class file won’t be incorporated
into the test SWF. If you test, you will test old code in the previously saved class
file(s).
After an error-free test, you should see enemy at the top left of the stage, with half of
enemy to the left of the stage and half of enemy above the stage. If you don’t see
player, use the trace() function to determine whether player is on-stage. Hint: All
DisplayObjects have a non-null stage property if they are on-stage.
trace(player.stage); // [object Stage] if on-stage, else null
87
88
Chapter 5 n Using the Flash API and Starting a Flash Game
After confirming player is on-stage, check player’s x and y properties to confirm that
it’s at the same location as enemy, which explains why we don’t see player—it’s
beneath enemy. Because addChild adds a child above all other children already in
the parent, the first child added will be beneath the second added child.
Because we didn’t assign an x or y property for either player or enemy, they are both
at 0,0, the default location when those properties aren’t assigned. More precisely,
their registration points are at 0,0.
We’ll fix that so those two MovieClips aren’t on top of each other and add keyboard
controls so we can control player. If you have no clue what class you should use to
add keyboard controls, don’t worry. This is where you start learning to use the Flash
API. You should have the API open in your browser.
Step 2
Check for something obvious that you can use—say, a Keyboard class or something
similar. Scroll down the Flash API to find the Keyboard class and see whether it
meets your needs (detecting when a keyboard key is pressed and determining which
key is pressed). Nearby, you might see the KeyboardType class and think that looks
good. Click the KeyboardType link and read to see whether you think it might work.
If you do, you’ll see that the KeyboardType class isn’t about keyboard key typing. It has
something to do with different types of keyboards or if no keyboard is available. That
won’t work for our needs, so click the Keyboard class. Now, that looks good.
Sometimes the explanatory text about a class can be like reading Aramaic. But sometimes the explanation is clear, like the Keyboard class’s explanation. For most classes,
you’ll use the class constructor, but for the Keyboard class there is no constructor and
no explanation of how to use the class.
That’s no problem; scroll down past the list of events and look for sample code. If
you’re like me, finding sample code is a major bonus. I often find it easier to copy
and paste sample code and then tweak it to suit my needs than to figure out how to
write code by reading the properties, methods, and events.
The first sample code is about a virtual keyboard that looks unhelpful, so keep scrolling. I see nothing useful. Go back to the top of the Keyboard class and see whether
there’s a link to something useful or whether you’ll need to look for another likely
class. (See Figure 5.4.)
Actually, both looking for a link and checking for another class will work. You can
click the Capturing Keyboard Input link at the top of the Keyboard class, just below
Step 2
Helpful-looking link
Figure 5.4
Top of Keyboard
class listing.
Source: Adobe Systems Incorporated.
Another
helpfullooking
link
Figure 5.5
Top of KeyboardEvent
class listing.
Source: Adobe Systems Incorporated.
More Examples, to see an example of how to use the Keyboard class (see Figure 5.4)
or look at the only other likely helpful class—the KeyboardEvent class, which has a
good explanation about adding a KeyboardEvent listener to the stage and has sample
code. (See Figure 5.5.)
This is a good spot to open another FLA and test keyboard-handling code to make
sure you understand how to capture keyboard input.
89
90
Chapter 5 n Using the Flash API and Starting a Flash Game
In addition to capturing keyboard input to control the PlayerTank instance, we want
to position both tanks so they are entirely on-stage. That means we want their x
properties to be at least half their width and their y properties to be at least half
their height. The maximum x and y depend on the stage size. To determine the
stage size, use the API to check the Stage class.
For the Stage class, there is a width, a height, a stageWidth, and a stageHeight. Reading
about those properties helps, but it’s still confusing. So, open a new FLA, add the
following to the Actions panel:
trace(stage.width,stage.stageWidth);
and test. You can see zero and the actual width of the stage that is listed in the Properties panel. That tells us that we want to use stage.stageWidth and stage.stageHeight,
but we want to understand what stage.width is, so draw a few things on-stage (and
on the pasteboard). Now the help file information will make sense, and you can
understand how both work. We still want to use stage.stageWidth, though.
returns the width of the DisplayObjects on- and off-stage when it executes, and stage.stageWidth returns the width of the stage displayed in the Properties
panel when the stage is selected. If you still don’t understand stage.width, open Flash
and test until you understand.
stage.width
We want to position the tanks using some randomness. This is where you need experience or help because it’s not obvious what class to look for when you want to add
an element of randomness using ActionScript. That is why I mentioned
Math.random() in Chapter 4. We need to use the Math class.
So, let’s add that keyboard event listener, position the two tanks, and test the code.
Version 0b
Main.as
package {
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
public class Main extends MovieClip {
public function Main() {
var player:PlayerTank = new PlayerTank();
positionF(player);
var enemy:EnemyTank = new EnemyTank();
positionF(enemy);
// Main is our document class, so we don’t need
Version 0c
// to use an Event.ADDED_TO_STAGE listener to
// ensure the stage property is available
stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownF);
}
// The code in positionF() positions mc completely
// on-stage if mc has registration point at its center.
// I created a new fla to test the code when Math.random()
// was 0 and when it was 1 to ensure I coded this
// correctly. And again, if you know nothing about
// ActionScript and want to position the tanks on-stage,
// check the API. For all DisplayObjects including
// MovieClips it lists x, y, width, and height properties
// you can use.
private function positionF(mc:MovieClip):void {
addChild(mc);
mc.x = int(mc.width/2+(stage.stageWidth-mc.width)*Math.random());
mc.y = int(mc.height/2+(stage.stageHeightmc.height)*Math.random());
}
private function keyDownF(e:KeyboardEvent):void {
trace(e);
}
}
}
Now, we need to determine which keys will move player forward and back and
which keys will turn player to the left and right. We could check the KeyboardEvent
class and use the constants that correspond to certain keys, but it’s easier to just use
the trace(e) shown; click the left arrow, up arrow, right arrow, and down arrow; and
note the keyCode properties traced (37,38,39,40, respectively).
So, here’s the next version of
way we want.
Main
that confirms we have our key codes defined the
Version 0c
Main.as
package {
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
public class Main extends MovieClip {
public function Main() {
var player:PlayerTank = new PlayerTank();
positionF(player);
91
92
Chapter 5 n Using the Flash API and Starting a Flash Game
var enemy:EnemyTank = new EnemyTank();
positionF(enemy);
stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownF);
}
private function positionF(mc:MovieClip):void {
addChild(mc);
mc.x = int(mc.width/2+(stage.stageWidth-mc.width)*Math.random());
mc.y = int(mc.height/2+(stage.stageHeightmc.height)*Math.random());
}
private function keyDownF(e:KeyboardEvent):void{
switch(e.keyCode){
case 37:
trace(“L”);
break;
case 38:
trace(“U”);
break;
case 39:
trace(“R”);
break;
case 40:
trace(“D”);
break;
}
}
}
}
We can test that and notice the keyboard keys are working, but you’ll also find that
the tanks can overlap, and we don’t want to allow that.
So, let’s work on the two tanks’ initial positioning so that if they overlap, one is repositioned. Make sure your stage is at least three times wider than player.width plus
enemy.width and three times higher than player.height plus enemy.height so this
code doesn’t create an endless loop.
There is no overlap property or method, but if you search the MovieClip API, you’ll
find the hitTestPoint() and hitTestObject() methods that should look promising.
After reading the API about those two methods, you should see that hitTestObject()
looks as if it will work for checking when one MovieClip overlaps (or hits) another
MovieClip.
Version 0d
If you don’t find anything that looks helpful (and that’s easy to do with so many
properties and methods listed), use a search engine. Searching for “Flash as3 overlap
test” should focus your attention on hitTestObject() and hitTestPoint().
And, if you fail to find the needed property, method, or event by scanning the
API and using a search engine, you can use the appropriate Flash forum for help.
Chapter 1 lists the links.
Also, we want to replace those keyDownF() traces with code that moves and rotates
player, and we want to make sure player cannot be moved off-stage. Checking the
MovieClip API reveals there is a rotation property we can use to rotate the tanks.
Here is the next version of the
Main
class.
Version 0d
Main.as
package {
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
public class Main extends MovieClip {
private var speed:int = 2;
private var rotationRate:int = 3;
public function Main() {
var player:PlayerTank = new PlayerTank();
positionF(player);
var enemy:EnemyTank = new EnemyTank();
positionF(enemy);
while(player.hitTestObject(enemy)){
positionF(enemy);
}
stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownF);
}
private function positionF(mc:MovieClip):void {
addChild(mc);
mc.x = int(mc.width/2+(stage.stageWidth-mc.width)*Math.random());
mc.y = int(mc.height/2+(stage.stageHeightmc.height)*Math.random());
}
private function keyDownF(e:KeyboardEvent):void{
switch(e.keyCode){
case 37:
trace(“L”);
player.rotation -= rotationRate;
93
94
Chapter 5 n Using the Flash API and Starting a Flash Game
case 38:
trace(“F”);
// Move player forward in the same direction as
// player’s rotation
player.x += Math.cos(player.rotation*Math.PI/180);
player.y += Math.sin(player.rotation*Math.PI/180);
break;
case 39:
trace(“R”);
player.rotation += rotationRate;
break;
case 40:
trace(“B”);
// Move player backward
player.x += Math.cos(-player.rotation*Math.PI/180);
player.y += Math.sin(-player.rotation*Math.PI/180);
break;
}
boundaryCheckF();
}
private function boundaryCheckF():void{
if(player.x<player.width/2){
player.x = player.width/2;
} else if(player.x>stage.stageWidth-player.width/2){
player.x = stage.stageWidth-player.width/2;
}
if(player.y<player.height/2){
player.y = player.height/2;
} else if(player.y>stage.stageHeight-player.height/2){
player.y = stage.stageHeight-player.height/2;
}
}
}
}
Test this, and you’ll promptly get many 1120: Access of Undefined Property xxxx
errors. This is one of the errors discussed previously and in Appendix A. We made
player and enemy local to the constructor Main(). We should remedy that by declaring those variables outside the constructor (#4 in the list at the start of Chapter 3
about class file structure).
Once that is done, you’ll find all sorts of problems. First, unless your _tank mc is heading toward the positive x-axis, your tank won’t appear to be moving forward when
Version 0d
the up arrow is pressed. Fix that by making sure the front of your tank is heading to
the right (0 degrees and radians) and then test.
Second, the left arrow causes the tank to rotate in a very strange manner. Fortunately,
we left our L,U,R,D traces during this test, so it’s obvious that when we press the left
arrow (and see L,F,L,F,L,F…), both case 37 and case 38 are executing. Checking our
code, you can see that we omitted a
break;
at the end of case 37.
So, let’s fix that and test. Now you’ll notice that our tank doesn’t go back correctly. A
check of our code will reveal faulty logic. When the back key is pressed, we don’t
want our tank to move along the opposite angle (-angle). We want it to move in
the opposite direction. Fix that and test.
Everything works the way it should, but there’s a major problem. Actually, there is
more than one major problem, and there are some minor problems, but I’ll address
the biggest problem first: The Main class is quickly becoming a mess.
We have all this code for controlling player in Main, and we’ll need more code, which
will make Main an even bigger mess. In addition, we’ll need to add an introduction
view (by view, I mean what is presented on-stage) to give some information about
the game, and we should probably allow users to customize the keys they use for
movement. Not everyone likes using the arrow keys. (Heck, I don’t like using the
arrow keys.) In addition, we’ll need a game-over view eventually.
We’re heading for a major mess in Main. So, while we can continue to put all of our
coding in Main, that’s just like putting all of our code in frame 1 of the main timeline,
and that bypasses one of the biggest advantages of OOP: encapsulation.
By encapsulation, I mean that each class should contain only the information it needs
to do its job. We want our Main class to control which views are presented on-stage.
When the game starts, Main will add an intro view. When the user is finished with the
intro view, Main will remove the intro view and add the game view (where the player
and enemy tanks will fight), and when the game is over, Main will remove the game
view and add the game over view.
We’ll use an IntroView class to control what happens in the introduction view, and
we’ll use a GameView class to monitor what happens when player and enemy fight
and an EndView to control what is displayed when combat is over—maybe the user’s
score or a taunting message.
95
96
Chapter 5 n Using the Flash API and Starting a Flash Game
We don’t want the GameView class to control the fighting. We want all the code that
controls player to be in the PlayerTank class and all the code that controls enemy to
be in the EnemyTank class. That will simplify Main so it’s easier to work with, will provide better encapsulation, and will make it easier and faster to find the code we need
to edit when changes and additions are needed.
While we’re simplifying the code, we’ll create a keyboard controls class (KBcontrols)
to store the key codes for moving left, right, up, and down. We’ll use static methods
so we can assign those key codes in IntroView and use them in PlayerTank.
We don’t want to be forced to pass those key codes back and forth, because we’re
going to have one PlayerTank instance in IntroView (so users can test the keyboard
controls) and a different PlayerTank instance in GameView. In addition, we might later
want to allow users to redefine the keyboard controls while in GameView or elsewhere.
We could make KBcontrols a singleton class, but I think we should make it a class
with static properties.
Actually, we already used a singleton class in Chapter 4 for a shape-based hit test in
the BitmapData section. So, showing the other way to make data easily available
among several classes is reasonable.
We could add quite a few things (such as scoring, a timer, enemy intelligence, and so
on), and we don’t know exactly what classes we’re going to have at the end of this
project, and we sure don’t know all the code that will be in each class that we
may need. That may be a little unsettling, but it’s how applications and games evolve
(at least, when I’m the developer).
While we’re getting this project properly organized, let’s put the class files in their
own directory so we don’t end up with a mess of files all in one directory. By convention, class files created by someone with website www.xyz.com should be placed
in an xyz subdirectory of a com directory. Because my programming website is www.
kglad.com, I’m going to put these class files in com/kglad.
If you recall from Chapter 3, that means my class files will use the com.kglad package
designation. It also means that any object in the library (such as _player tank mc) will
need its class renamed from PlayerTank to com.kglad.PlayerTank, and our document
class Main will need to be changed to com.kglad.Main.
If we fail to indicate our document class file’s location in the Properties panel, the
document class won’t be used. We’ll test our FLA, and nothing will happen. We
would then put a trace(“Main”) in the Main class constructor, and we’d see no
output.
Version 0d
That would remind us that we have a path problem with our document class and
prompt us to fix the class path in the Properties panel. Likewise, if we have a class
path problem when we start trying to use a class file like PlayerTank, we’ll find (using
the trace() function again) that the class file isn’t being used. That would remind us
to fix that library path.
Finally, following is the code from the refactored classes. These classes are on this
book’s companion website (www.courseptr.com/downloads). Copy the tank_combat
directory from the site to a subdirectory on your hard drive so you can manipulate
the files. For the remainder of this chapter, all the files mentioned will be referencing
your hard drive files.
In tank_combat, open tank_combat_01.fla in your Flash Pro. In the com.kglad subdirectory, open Main_01.as, IntroView_01.as, KBcontrol_01.as, and PlayerTank_01.as.
Save those four pseudo class files as Main.as, IntroView.as, KBcontrol.as, and
PlayerTank.as, respectively. You should now have the four classes (Main, IntroView,
KBControl, and PlayerTank) open in Flash Pro (or your preferred class-editing software) where they’re easy to read and test.
I refer to the ClassName_01.as files as pseudo class files because they aren’t true class
files. If they were, they would need to define a ClassName_01 class. For example, if you
changed the document class in the FLA to use Main_01, you should see a 5008 error
message because a class file named Main_01 should be defining a Main_01 class, not a
Main class.
I created those pseudo classes to make it easy for you to follow the discussion. I don’t
typically make pseudo classes like that when I code.
After you save each of those four pseudo classes with the correct class name, there
shouldn’t be any 5008 errors, and you should have four proper classes. Don’t worry
if those four classes already exist. Overwrite them. You’ll be using the various pseudo
class file versions to create different versions of Main, IntroView, KBcontrol, and
PlayerTank as you read and as this game evolves.
The code displayed in your Flash Pro should match the following code, though you
may find additional comments here. The comments provide additional information
that you should read if you have any questions about the code. You should test this
code (after saving those classes) by clicking Test > Flash Movie > in Flash
Professional.
97
98
Chapter 5 n Using the Flash API and Starting a Flash Game
Version _01
Version _01 Main.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
// Because Main is in the com.kglad package,
// I don’t need import other com.kglad
// package classes like IntroView
public class Main extends MovieClip {
// To record the current view for possible
// later use when this class might be adding
// and removing more than intro, game and end-game views
private var currentView:MovieClip;
public function Main() {
// this constructor is called as soon as the
// game starts and the first thing I do is
// add an IntroView instance in addIntroViewF();
addIntroViewF();
}
private function addIntroViewF():void{
// here introView is created (but this
// reference is local)
var introView:IntroView = new IntroView();
// I’m adding a listener to check if and
// when the start game button is clicked.
// That button has its listener defined in
// IntroView and, when clicked, dispatches
// an event, “startgameE”.
introView.addEventListener(“startgameE”,addGameViewF);
// I’m saving the current view
currentView = introView;
// add introView to the display
addChild(introView);
}
private function addGameViewF(e:Event):void{
// nothing here yet, but this will be where
// I create a GameView
}
}
}
The _01 version of the IntroView class follows. It is the class associated with the
library MovieClip symbol intro view.
Version _01
Version _01 IntroView.as
package com.kglad {
// import the needed classes
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import flash.events.Event;
public class IntroView extends MovieClip {
public function IntroView() {
// Wait until this is added to the display list,
// though in this particular class I do not need
// this. But it’s a good habit to use it for
// display objects other than the document class.
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Define all the click listeners
listenersF();
// Add a PlayerTank instance to introView so
// users can test the keyboard controls
addPlayerTankF();
}
// Define the click listeners
private function listenersF():void{
left_mc.addEventListener(MouseEvent.CLICK,leftF);
// Because I used MovieClips for my buttons,
// they don’t act like buttons. The mouse does
// not respond like it is over a button.
// I need to find a MovieClip property that will
// make my MovieClip buttons act like buttons.
// The buttonMode property is promising. So is
// useHandCursor, but after testing, buttonMode
// is the property that should be used.
left_mc.buttonMode = true;
right_mc.addEventListener(MouseEvent.CLICK,rightF);
right_mc.buttonMode = true;
forward_mc.addEventListener(MouseEvent.CLICK,forwardF);
forward_mc.buttonMode = true;
back_mc.addEventListener(MouseEvent.CLICK,backF);
back_mc.buttonMode = true;
startgame_mc.addEventListener(MouseEvent.CLICK,startgameF);
startgame_mc.buttonMode = true;
}
99
100
Chapter 5 n Using the Flash API and Starting a Flash Game
private function addPlayerTankF():void{
// playerBG_mc is a MovieClip already added to
// the intro view library MovieClip used to
// restrict player’s movement
var w:int = playerBG_mc.width;
var h:int = playerBG_mc.height
// Create a player instance passing w and h
// used to restrict player’s movement.
// Movement restriction done in PlayerTank.
var player:PlayerTank = new PlayerTank(w,h);
positionF(playerBG_mc,player);
}
private function positionF(parent_mc:MovieClip,mc:MovieClip):void {
parent_mc.addChild(mc);
// Position player in the middle of playerBG_mc
mc.x = int(parent_mc.width/2);
mc.y = int(parent_mc.height/2);
}
// Click listener functions used to define keydown
// listeners. That is, after the user clicks left_mc,
// the next keyboard key pressed will be used as
// player’s left control key.
private function leftF(e:MouseEvent):void{
stage.addEventListener(KeyboardEvent.KEY_DOWN,leftKeyF);
}
private function rightF(e:MouseEvent):void{
stage.addEventListener(KeyboardEvent.KEY_DOWN,rightKeyF);
}
private function forwardF(e:MouseEvent):void{
stage.addEventListener(KeyboardEvent.KEY_DOWN,forwardKeyF);
}
private function backF(e:MouseEvent):void{
stage.addEventListener(KeyboardEvent.KEY_DOWN,backKeyF);
}
// Finally, key listeners to assign key codes in KBcontrols
private function leftKeyF(e:KeyboardEvent):void{
// Because leftKeyCode is a static setter, it’s
// used like this:
KBcontrols.leftKeyCode = e.keyCode;
// remove the keydown listener
stage.removeEventListener(KeyboardEvent.KEY_DOWN,leftKeyF);
}
private function rightKeyF(e:KeyboardEvent):void{
KBcontrols.rightKeyCode = e.keyCode;
Version _01
stage.removeEventListener(KeyboardEvent.KEY_DOWN,rightKeyF);
}
private function forwardKeyF(e:KeyboardEvent):void{
KBcontrols.forwardKeyCode = e.keyCode;
stage.removeEventListener(KeyboardEvent.KEY_DOWN,
forwardKeyF);
}
private function backKeyF(e:KeyboardEvent):void{
KBcontrols.backKeyCode = e.keyCode;
stage.removeEventListener(KeyboardEvent.KEY_DOWN,backKeyF);
}
private function startgameF(e:MouseEvent):void{
// The “startgameE” event is dispatched to
// all IntroView instances that have a
// “startgameE” listener. (The only instance
// created is in Main and it has this listener.)
dispatchEvent(new Event(“startgameE”));
}
}
}
Here is the _01 version of the
trol PlayerTank instances.
KBcontrol
class that is used to store the keys that con-
Version _01 KBcontrols.as
package com.kglad {
public class KBcontrols{
// variable declarations and default key codes
private static var _leftKeyCode:int = 37;
private static var _rightKeyCode:int = 39;
private static var _forwardKeyCode:int = 38;
private static var _backKeyCode:int = 40;
public function KBcontrols() {
// constructor code
// There’s nothing here because there
// will be no KBcontrol instances. This
// class is used by other classes to store
// and retrieve key codes.
}
// static getters
public static function get leftKeyCode():int{
return _leftKeyCode;
}
public static function get rightKeyCode():int{
101
102
Chapter 5 n Using the Flash API and Starting a Flash Game
return _rightKeyCode;
}
public static function get forwardKeyCode():int{
return _forwardKeyCode;
}
public static function get backKeyCode():int{
return _backKeyCode;
}
// static setters with some (but, at this point,
// not much) error checking
public static function set leftKeyCode(n:int):void{
if(keyCodeF(n)){
_leftKeyCode = n;
}
}
public static function set rightKeyCode(n:int):void{
if(keyCodeF(n)){
_rightKeyCode = n;
}
}
public static function set forwardKeyCode(n:int):void{
if(keyCodeF(n)){
_forwardKeyCode = n;
}
}
public static function set backKeyCode(n:int):void{
if(keyCodeF(n)){
_backKeyCode = n;
}
}
private static function keyCodeF(n:int):Boolean{
if(n>=37&&n<=105){
return true;
} else {
return false;
}
}
}
}
And finally, here is the _01 version of the
Version _01 PlayerTank.as
package com.kglad {
PlayerTank
class:
Version _01
// again, import the needed classes
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
public class PlayerTank extends MovieClip {
// Define a variable, speed, used to control
// speed of movement.
private var speed:int = 3;
// Define a variable, rotationRate, to control
// the rate of rotation.
private var rotationRate:int = 3;
// These are movement limits to keep instances
// within a confined rectangle. In introView
// this is defined using playerBG_mc and when we
// create a GameView instance these limits will
// be defined by stage’s stageWidth and
// stageHeight property. Or, even better,
// I can put a display at the top of the GameView
// instance which will display the score and time
// and I’ll restrict movement using a game view
// rectangle similar to the one used in intro view
// so it doesn’t interfere with the score/time display
private var xLimit:int;
private var yLimit:int;
public function PlayerTank(_xLimit:int,_yLimit:int) {
// The movement limits are passed in the constructor
// and used in the below moveLimitsF()
xLimit = _xLimit;
yLimit = _yLimit;
// The usual listener needed to ensure this is
// added to the stage before executing code that
// depends on display list objects. In PlayerTank,
// this listener is needed or I’ll trigger a 1009 null
// object reference when I try to reference “stage”.
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Listener for keydown events
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
}
// Keydown listener function that checks if the pressed key
// matches a control key and then updates the PlayerTank
// instance’s x and y and rotation properties
private function keydownF(e:KeyboardEvent):void{
103
104
Chapter 5 n Using the Flash API and Starting a Flash Game
if(KBcontrols.leftKeyCode==e.keyCode){
this.rotation -= rotationRate;
}
if(KBcontrols.rightKeyCode==e.keyCode){
this.rotation += rotationRate;
}
if(KBcontrols.forwardKeyCode==e.keyCode){
this.x += speed*Math.cos(this.rotation*Math.PI/180);
this.y += speed*Math.sin(this.rotation*Math.PI/180);
}
if(KBcontrols.backKeyCode==e.keyCode){
this.x -= speed*Math.cos(this.rotation*Math.PI/180);
this.y -= speed*Math.sin(this.rotation*Math.PI/180);
}
// Check if a move limit has been exceeded.
moveLimitsF();
}
private function moveLimitsF():void{
if(this.x<this.width/2){
this.x = this.width/2;
}else if(this.x>xLimit-this.width/2){
this.x = xLimit-this.width/2;
}
if(this.y<this.height/2){
this.y = this.height/2;
} else if(this.y>yLimit-this.height/2){
this.y = yLimit-this.height/2;
}
}
}
}
To test this code, open tank_combat_01.fla in Flash Pro and click Control > Test
Movie > in Flash Professional.
Test the movement of the PlayerTank instance. It’s terrible. When you press and hold
one of the controls, the character moves a little and then there’s a delay followed by
steady stuttering movement. On a positive note, the buttons used to define the custom move and rotation keys work well.
But the stuttering movement is a major problem. We can smooth out some of
the stuttering movement by increasing the FLA’s frame rate in the Properties panel,
but that initial delay after pressing and holding the control key is due to the delay in
the keyboard repeat delay. And the frequency of calls to keydownF() is dependent on
Version _01
the keyboard’s repeat rate. Neither the repeat delay nor the repeat rate are controllable using ActionScript.
So, we cannot use a keyboard listener to trigger smooth movement. We can use the
keyboard listener to detect key presses, but for movement we should use an
enterFrame or Timer loop. (See the Chapter 4 section about loops.) I’m going to try
an enterFrame loop.
And here’s another benefit of encapsulation: We don’t have to wade through so
much code to find the relevant code that needs to be edited. The code used to control
player’s movement and rotation is in PlayerTank.as.
This code has advanced to a more typical starting point for an experienced developer.
The initial versions displayed common approaches to coding taken by beginning
coders and displayed the major issue with putting all your code in the document
class.
I will no longer discuss how to use the Flash API. I don’t expect that you are familiar
with all or even most of it. But you should now know how to use the API to find
what you need and how to use other resources when you cannot find what you
need in the API.
You will probably need the Flash API open to reference when creating a Flash game.
I know that I always have to open it, sooner or later, for any major project.
Using other APIs is very similar to using the Flash API. I’ll discuss some other APIs
in subsequent chapters.
Discussion of the tank combat game is ready to advance to the next chapter, where
we’ll continue to develop the game, again showing one way to develop a game and
how to debug the problems that occur during development.
105
This page intentionally left blank
Chapter 6
Developing a Flash Game
We ended the previous chapter with version _01 of a tank combat game. In this
chapter, we’ll continue development of that game.
The main purpose of this chapter is to show you how a game typically evolves. Well,
I don’t really know if that previous sentence is true. What I do know is that this
chapter will show you how a game I develop typically evolves.
That means there will be problems, errors, and missteps that need to be handled and
debugged. I’m going to point those out between game versions and show you one
way to remedy them, but I will no longer explicitly discuss using the ActionScript
3.0 API Language Reference (previously and henceforth called the Flash API).
I used the Flash API repeatedly to code these game versions, and I expect you will
need to use it repeatedly to follow this code, but you should now know how (see
Chapter 5, “Using the Flash API and Starting a Flash Game”) and when (each and
every time you encounter code with which you aren’t familiar) to use it.
For the next update, open PlayerTank_02.as and save as PlayerTank.as. None of the
other classes has any changes. If you compare the _01 and _02 file sizes, you can see
all the classes are the same except PlayerTank.
Nevertheless, I created _02 versions of all four class files to make it easy to keep track
of which classes are used with which game version. You can open all the _02 pseudo
class files and save them as proper class files or just leave the previously created Main,
IntroView, and KBcontrol intact.
107
108
Chapter 6 n Developing a Flash Game
I also made an _02 version of the FLA, which contains changes in _tank mc. I set up
_tank mc so I could use it as a player tank and as an enemy tank by applying color to
the base and turret top of the tanks to distinguish them. I did that to give me flexibility if I want to create different types of enemy tanks, perhaps with different speed
and rotation rate and different point values once I start adding scoring.
Here is version _02 of PlayerTank.
Version _02
Version _02 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
public class PlayerTank extends MovieClip {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var directionS:String;
public function PlayerTank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Listener for keydown events to start an enterframe loop
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
// Listener for keyup events to stop the enterframe loop
stage.addEventListener(KeyboardEvent.KEY_UP,keyupF);
}
private function keydownF(e:KeyboardEvent):void{
// Add/start enterframe loop
this.addEventListener(Event.ENTER_FRAME,enterframeF);
if(KBcontrols.forwardKeyCode==e.keyCode){
directionS = "forward";
}
if(KBcontrols.backKeyCode==e.keyCode){
directionS = "back";
}
if(KBcontrols.leftKeyCode==e.keyCode){
directionS = "left";
Version _02
}
if(KBcontrols.rightKeyCode==e.keyCode){
directionS = "right";
}
}
private function keyupF(e:KeyboardEvent):void{
// Remove/end enterframe loop
if(this.hasEventListener(Event.ENTER_FRAME)){
this.removeEventListener(Event.ENTER_FRAME,enterframeF);
}
}
private function enterframeF(e:Event):void{
switch(directionS){
case "forward":
this.x += speed*Math.cos(this.rotation*Math.PI/180);
this.y += speed*Math.sin(this.rotation*Math.PI/180);
break;
case "back":
this.x -= speed*Math.cos(this.rotation*Math.PI/180);
this.y -= speed*Math.sin(this.rotation*Math.PI/180);
break;
case "left":
this.rotation -= rotationRate;
break;
case "right":
this.rotation += rotationRate;
break
}
moveLimitsF();
}
private function moveLimitsF():void{
if(this.x<this.width/2){
this.x = this.width/2;
} else if(this.x>xLimit-this.width/2){
this.x = xLimit-this.width/2;
}
if(this.y<this.height/2){
this.y = this.height/2;
} else if(this.y>yLimit-this.height/2){
this.y = yLimit-this.height/2;
}
}
}
}
109
110
Chapter 6 n Developing a Flash Game
If you test version _02, you will see movement is much improved. There is no longer
a delay between pressing a key and the tank responding. But if you press another key
while one key is pressed, or if you press two keys at the same time, the tank only
responds to one key. And, if you key up on one key while another is pressed, the
enterFrame loop stops.
The failure to respond to key-press combinations (such as forward and left) is
because the code stores only the last pressed key in a string, directionS. We’ll need
to use an array to store more than one key press at any one time. You should check
the Array class in the Flash API to see what properties and methods are available.
Tank movement stops when there is a key-up, even while another key is pressed,
because we have a key-up listener function (keyupF()) that doesn’t check whether
another key is pressed. It removes the enterFrame loop whenever there is a key-up
and an existing loop.
The tank also doesn’t stop when a boundary is encountered. We need to fix the
moveLimitsF() function so it not only resets the x or y when a boundary limit is
reached, but it also resets the x and y. To do that, we can store the previous x, y
(before boundary check).
For version _03, I updated
Kbcontrols
and
PlayerTank.
Version _03
Version _03 KBcontrols.as
package com.kglad {
public class KBcontrols {
// These are the default arrow-key keyCodes
private static var _forwardKeyCode:int = 38;
private static var _backKeyCode:int = 40;
private static var _leftKeyCode:int = 37;
private static var _rightKeyCode:int = 39;
// I’m using a static array to store the keycodes.
// A keyCode’s index in _keyCodeA is crucial to determine
// the tank’s response to that keyCode.
// The first element (0-index) is forward, next back, then
// left, then right.
private static var _keyCodeA:Array =
[_forwardKeyCode,_backKeyCode,_leftKeyCode,_rightKeyCode];
public function KBcontrols() {
// constructor code
Version _03
}
// keyCodeA is read-only; i.e., there is no setter for it.
public static function get keyCodeA():Array{
return _keyCodeA;
}
// The 4 controls can be set but there is no need for a
// getter for them.
public static function set forwardKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[0] = n
}
}
public static function set backKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[1] = n;
}
}
public static function set leftKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[2] = n;
}
}
public static function set rightKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[3] = n;
}
}
private static function keyCodeF(n:int):Boolean{
if(n>=37&&n<=105){
return true;
} else {
return false;
}
}
}
}
Version _03 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
public class PlayerTank extends MovieClip {
private var speed:int = 3;
111
112
Chapter 6 n Developing a Flash Game
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var prevX:Number;
private var prevY:Number;
private var keyCodeIndex:int;
private var directionA:Array = [];
private var boundaryViolation:Boolean;
public function PlayerTank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Initial values for prevX and prevY, though
// these aren’t needed unless this instance
// starts on a boundary.
prevX = this.x;
prevY = this.y;
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.addEventListener(KeyboardEvent.KEY_UP,keyupF);
}
// I revised KBcontrols so it stores the 4 tank control
// keycodes in a static array keyCodeA.
// The first element (with index 0) is the forward keycode;
// the 2nd, 3rd, and 4th elements are back, left, and right, resp.
// Using an array (keyCodeA) allows me to avoid using for loops
// to detect if a pressed key is a control key.
private function keydownF(e:KeyboardEvent):void{
// Here is precisely how using keyCodeA eliminates the
// need for a for loop: arrays have an indexOf property
// that allows an easy-to-code way to check if a value
// is an array element.
// In addition, it returns the index of the found value.
// Because the 0-index is the forward key and 1-index
// is the back key, I can determine not only if a control
// key was pressed, but exactly which control key.
keyCodeIndex = KBcontrols.keyCodeA.indexOf(e.keyCode);
// If keyCodeIndex==-1, e.keyCode is not in keyCodeA.
// Otherwise, it is and its value is the index of
// e.keyCode in keyCodeA
if(keyCodeIndex>-1){
// By checking which directionA elements are true,
// I can determine which control keys are
Version _03
// currently pressed.
directionA[keyCodeIndex] = true;
}
this.addEventListener(Event.ENTER_FRAME,enterframeF);
}
private function keyupF(e:KeyboardEvent):void{
// Same code as above. Only in keyupF, I’m going to set
// the corresponding value in directionA to false to
// indicate this key is not currently being pressed.
keyCodeIndex = KBcontrols.keyCodeA.indexOf(e.keyCode);
if(keyCodeIndex>-1){
directionA[keyCodeIndex] = false;
}
// Only need and want to check for enterframe removal
// if enterframe listener has been added
if(this.hasEventListener(Event.ENTER_FRAME)){
// Check if any control key is pressed and,
// if not, remove the enterframe loop.
if(directionA.indexOf(true)==-1){
this.removeEventListener(Event.ENTER_FRAME,enterframeF);
}
}
}
private function enterframeF(e:Event):void{
// No if-else statements here now. I want to
// change rotation and position if two
// appropriate control keys are pressed together.
if(directionA[0]){
//trace("foreward");
this.x += speed*Math.cos(this.rotation*Math.PI/180);
this.y += speed*Math.sin(this.rotation*Math.PI/180);
}
if(directionA[1]){
//trace("back");
this.x -= speed*Math.cos(this.rotation*Math.PI/180);
this.y -= speed*Math.sin(this.rotation*Math.PI/180);
}
if(directionA[2]){
//trace("left");
this.rotation -= rotationRate;
}
if(directionA[3]){
//trace("right");
this.rotation += rotationRate;
113
114
Chapter 6 n Developing a Flash Game
}
// Check for boundary violation. Notice prevX,prevY
// are updated after moveLimitsF() executes.
moveLimitsF();
prevX = this.x;
prevY = this.y;
}
private function moveLimitsF():void{
// Check if any of the 4 boundaries are violated.
// If one is, no need to check others, so if-else
// appropriate here.
boundaryViolation = false;
if(this.x<this.width/2){
boundaryViolation = true;
} else if(this.x>xLimit-this.width/2){
boundaryViolation = true;
} else if(this.y<this.height/2){
boundaryViolation = true;
} else if(this.y>yLimit-this.height/2){
boundaryViolation = true;
}
// If there is a boundary violation, reset both
// x,y to the pre-violation values.
if(boundaryViolation){
this.x = prevX;
this.y = prevY;
}
}
}
}
That is progress. No errors, and everything works pretty well. Let’s add more
features.
Next, we’ll add turret rotation and shooting. Thinking about that made me realize we
should change turret_mc’s registration point so it’s in the center of the turret top,
because we want the turret to rotate around that point. And, to make coding less
complicated, we want our turret pointing toward the right, which is aligned with
rotation angle zero. Those changes aren’t necessary, but they make coding easier.
I tested a few ways to control turret rotation (using the mouse’s x-position and using
keyboard keys) and decided those would be too difficult. There will be almost no
way to beat the computer-controlled enemy tank unless aiming is easy and intuitive
Version _04
(or we code the enemy tank to do something stupid, such as aim randomly, but I’m
not planning to do that).
The enemy tank will have no problem moving and aiming at the same time, and
using either of our first two considerations would be difficult for a human. (Well,
they were difficult for me.) Therefore, I made a design decision and decided to use
the cursor as an aim point. That is, the turret will rotate to aim at the cursor. So, we
want to add a custom cursor that will look like a crosshair, and we need to create a
projectile to shoot.
For version _04, we’ll update only the PlayerTank class, and we’ll make changes to
only the first part of the class. We won’t make any changes in keydownF, keyupF,
enterframeF, or moveLimitsF. But we will update the FLA to add a custom cursor and
redo the tank turret.
Version _04
Version _04 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Mouse;
public class PlayerTank extends MovieClip {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var prevX:Number;
private var prevY:Number;
private var keyCodeIndex:int;
private var directionA:Array = [];
private var boundaryViolation:Boolean;
private var cCursor:CustomCursor;
private var shellA:Array = [];
private var gunL:Number;
public function PlayerTank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
115
116
Chapter 6 n Developing a Flash Game
private function init(e:Event):void{
// Initial values for prevX and prevY, though
// these aren’t needed unless this instance
// starts on a boundary.
prevX = this.x;
prevY = this.y;
// Tank gun length from center of turret top to muzzle
gunL = this.turret_mc.width-this.turret_mc.top_mc.width/2;
// Create a custom cursor
cCursor = new CustomCursor();
// Add a mouse down listener (to the tank’s parent,
// which is the rectangle within which the tank
// moves) to shoot
this.parent.addEventListener(MouseEvent.MOUSE_DOWN,shootF);
// When the mouse is over the tank’s parent, add
// cCursor and hide the mouse.
this.parent.addEventListener(MouseEvent.MOUSE_OVER,
addMouseTrackF);
// Remove cCursor and un-hide the mouse
this.parent.addEventListener(MouseEvent.MOUSE_OUT,
removeMouseTrackF);
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.addEventListener(KeyboardEvent.KEY_UP,keyupF);
}
private function shootF(e:MouseEvent):void{
// Create a shell
var shell:Shell = new Shell();
// Add it to an array because I know I’ll
// need to keep track of it and I think I
// want to allow more than 1 shell to exist
// at any one time.
shellA.push(shell);
// Add the shell to tank’s parent
this.parent.addChild(shell);
// Position the shell at the end of the
// muzzle. (See Figure 6.1.)
shell.x =
this.turret_mc.x+this.x+gunL*Math.cos(this.turret_mc.rotation*Math.PI/180);
shell.y =
this.y+gunL*Math.sin(this.turret_mc.rotation*Math.PI/180);
}
private function addMouseTrackF(e:MouseEvent):void{
// Check the Mouse class for something to hide the mouse
Mouse.hide();
Version _04
// Add cCursor to tank’s parent
this.parent.addChild(cCursor);
// Add a mousemove listener to update the cursor’s
// x,y properties and the turret’s rotation
this.parent.addEventListener(MouseEvent.MOUSE_MOVE,
turnTurretF);
}
private function removeMouseTrackF(e:MouseEvent):void{
// Check the Mouse class for something to
//un-hide the mouse
Mouse.show()
// Remove cCursor
this.parent.removeChild(cCursor);
// Remove the mousemove listener. We no longer
// want the turret to rotate and there’s no
// need to update cCursor’s x,y.
this.parent.removeEventListener(MouseEvent.MOUSE_MOVE,
turnTurretF);
}
private function turnTurretF(e:MouseEvent):void{
// Update cCursor’s x,y to be the same as the mouse’s
// x,y relative to tank’s parent
cCursor.x = e.localX;
cCursor.y = e.localY;
// Rotate the turret
this.turret_mc.rotation = 180*Math.atan2(e.localY-this.y,e.localXthis.x-this.turret_mc.x)/Math.PI;
}
private function keydownF(e:KeyboardEvent):void{
}
private function keyupF(e:KeyboardEvent):void{
}
private function enterframeF(e:Event):void{
}
private function moveLimitsF():void{
}
}
}
117
118
Chapter 6 n Developing a Flash Game
shell.x = Math.cos
(turret_mc.rotation*Math.PI/180)
shell.y = Math.sin
(turret_mc.rotation*Math.PI/180)
turret_mc.rotation
(in degrees). To convert
degrees to radians, multiply
by Math.PI/180.
gunL ( = gun length)
Figure 6.1
Use trigonometry to position shells at the muzzle’s end.
Source: © 2013 Keith Gladstien, All Rights Reserved.
And test.
The custom cursor is doing some funky jumping around, as if it’s being repeatedly
removed and added. We added a trace() to removeMouseTrackF() to see whether the
mouseout event is being triggered. (I thought the mouseout might be triggered if we
moused over the tank, but I didn’t expect it when moving over that parent rectangle.)
After checking with that trace(), we can see our mouseout is being triggered every
time we move the mouse unless we move the mouse slowly. The only thing that
could be doing that is the custom cursor!
We’re mousing over the custom cursor, and that is triggering a mouseout of the custom cursor’s parent. But, it doesn’t seem like the custom cursor should be lagging
that far behind the mouse. Let’s remove the mouseout trace() and add:
trace(getTimer(),cCursor.x,e.localX);
to
turnTurretF()
to see what happens when we move the mouse rapidly.
Version _05
Whoa, you can see the custom cursor’s position is lagging far behind the mouse even
though it updates every few milliseconds. I thought it might be updating infrequently
because of the MouseEvent.MOUSE_MOVE event, but the trace() reveals that isn’t the
issue. But, the mouse easily moves 30 pixels in less than 10 milliseconds with no
effort, causing the mouse to mouseover the custom cursor and thereby mouseout of
its parent rectangle.
One solution is to use a rollout event. The rollout event will be triggered only if you
roll out of the parent and all its children (which, so far, comprises the player’s tank
and the custom cursor).
And our shell placement looks good except when we rotate the tank. We forgot to
adjust the muzzle position when the tank is rotated. We need to rework it.
But just thinking about the muzzle position is giving me a headache because the rotation point for the tank and the rotation point of the turret are offset. We’ll need to do
some pencil and paper calculations to figure out the correct code, and then Flash will
be burdened with that unduly complex calculation.
However, we can simplify that calculation (for both Flash and us) if we make the
tank’s rotation point the same as the turret’s rotation point. That’s a better resolution
than using the more complex calculation needed if the tank and turret have different
rotation points.
Version _05 has an updated FLA and
unchanged from version _04.
PlayerTank
Version _05
Version _05 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Mouse;
public class PlayerTank extends MovieClip {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var prevX:Number;
private var prevY:Number;
private var keyCodeIndex:int;
class. The other classes remain
119
120
Chapter 6 n Developing a Flash Game
private var directionA:Array = [];
private var boundaryViolation:Boolean;
private var cCursor:CustomCursor;
private var shellA:Array = [];
private var gunL:Number;
public function PlayerTank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// initial values for prevX and prevY, though
// these aren’t needed unless this instance
// starts on a boundary.
prevX = this.x;
prevY = this.y;
// Tank gun length from center of turret top
// to muzzle
gunL = this.turret_mc.width-this.turret_mc.top_mc.width/2;
// Create a custom cursor
cCursor = new CustomCursor();
// Add a mouse down listener (to the tank’s
// parent, which is the rectangle within which
// the tank moves) to shoot
this.parent.addEventListener(MouseEvent.MOUSE_DOWN,shootF);
// When the mouse is over the tank’s parent,
// add cCursor and hide the mouse.
this.parent.addEventListener(MouseEvent.ROLL_OVER,
addCustomCursorF);
// Remove cCursor and un-hide the mouse
this.parent.addEventListener(MouseEvent.ROLL_OUT,
removeCustomCursorF);
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.addEventListener(KeyboardEvent.KEY_UP,keyupF);
}
private function shootF(e:MouseEvent):void{
// Create a shell
var shell:Shell = new Shell();
// Add it to an array because I know I’ll need to
// keep track of it and I think I want to allow
// more than 1 shell to exist at any one time.
shellA.push(shell);
// Add the shell to tank’s parent
this.parent.addChild(shell);
Version _05
// Position the shell at the end of the
// muzzle. (See Figure 6.2.)
shell.x =
this.x+gunL*Math.cos((this.turret_mc.rotation+this.rotation)*Math.PI/180);
shell.y =
this.y+gunL*Math.sin((this.turret_mc.rotation+this.rotation)*Math.PI/180);
}
private function addCustomCursorF(e:MouseEvent):void{
this.addEventListener(Event.ENTER_FRAME,rotateTurretF);
// Check the Mouse class for something
// to hide the mouse
Mouse.hide();
// Add cCursor to tank’s parent
this.parent.addChild(cCursor);
// Add a mousemove listener to update the
// cursor’s x,y properties and the turret’s rotation
//this.parent.addEventListener(MouseEvent.MOUSE_MOVE,
// rotateTurretF);
}
private function removeCustomCursorF(e:MouseEvent):void{
this.removeEventListener(Event.ENTER_FRAME,rotateTurretF);
// Check the Mouse class for something to un-hide
// the mouse
Mouse.show()
// Remove cCursor
this.parent.removeChild(cCursor);
// Remove the mousemove listener. We no longer want
// the turret to rotate and there’s no need to
// update cCursor’s x,y.
//this.parent.removeEventListener(MouseEvent.MOUSE_MOVE,
// rotateTurretF);
}
private function rotateTurretF(e:Event):void{
// Update cCursor’s x,y to be the same as the
//mouse x,y relative to tank’s parent
cCursor.x = this.parent.mouseX;
cCursor.y = this.parent.mouseY;
// Rotate the turret
this.turret_mc.rotation =
-this.rotation+180*Math.atan2(this.parent.mouseY-this.y,this.parent.mouseX-this.xthis.turret_mc.x)/Math.PI;
}
// I revised KBcontrols so it stores the 4 tank
// control keycodes in a static array keyCodeA
121
122
Chapter 6 n Developing a Flash Game
// The first element (with index 0) is the forward
// keycode; the 2nd, 3rd, and 4th elements are back,
// left, and right, resp.
// using a keyCodeA allows me to avoid using for
// loops to detect if a pressed key is a control key.
private function keydownF(e:KeyboardEvent):void{
// Here is precisely how using keyCodeA
// eliminates the need for a for loop:
// arrays have an indexOf property that
// allows a quick way to check if a value
// is an array element. In addition, it
// returns the index of the found value.
// Because the 0-index is the forward key
// and 1-index is the back key, I can
// efficiently determine not only if a
// control key was pressed, but exactly
// which control key.
keyCodeIndex = KBcontrols.keyCodeA.indexOf(e.keyCode);
// if keyCodeIndex==-1, e.keyCode is not
// in keyCodeA. Otherwise, it is and its value
// is the index of e.keyCode in keyCodeA
if(keyCodeIndex>-1){
// by checking which directionA elements
// are true, I can determine which control
// keys are currently pressed.
directionA[keyCodeIndex] = true;
}
this.addEventListener(Event.ENTER_FRAME,moveTankF);
}
private function keyupF(e:KeyboardEvent):void{
// Same code as above. Only in keyupF, I’m going
// to set the corresponding value in directionA
// to false to indicate this key is not currently
// being pressed.
keyCodeIndex = KBcontrols.keyCodeA.indexOf(e.keyCode);
if(keyCodeIndex>-1){
directionA[keyCodeIndex] = false;
}
if(directionA.indexOf(true)==-1){
this.removeEventListener(Event.ENTER_FRAME,moveTankF);
}
}
private function moveTankF(e:Event):void{
// No if-else statements here now. I want to
Version _05
// change rotation and position if two appropriate
// control keys are pressed together.
if(directionA[0]){
//trace("foreward");
this.x += speed*Math.cos(this.rotation*Math.PI/180);
this.y += speed*Math.sin(this.rotation*Math.PI/180);
}
if(directionA[1]){
//trace("back");
this.x -= speed*Math.cos(this.rotation*Math.PI/180);
this.y -= speed*Math.sin(this.rotation*Math.PI/180);
}
if(directionA[2]){
//trace("left");
this.rotation -= rotationRate;
}
if(directionA[3]){
//trace("right");
this.rotation += rotationRate;
}
// Check for boundary violation. Notice prevX,prevY
// are updated after moveLimitsF() executes.
moveLimitsF();
prevX = this.x;
prevY = this.y;
}
private function moveLimitsF():void{
// Check if any of the 4 boundaries are violated.
// If one is, no need to check others, so if-else
// appropriate here.
boundaryViolation = false;
if(this.x<this.width/2){
boundaryViolation = true;
} else if(this.x>xLimit-this.width/2){
boundaryViolation = true;
} else if(this.y<this.height/2){
boundaryViolation = true;
} else if(this.y>yLimit-this.height/2){
boundaryViolation = true;
}
// If there is a boundary violation, reset both x,y
// to the pre-violation values.
if(boundaryViolation){
this.x = prevX;
123
124
Chapter 6 n Developing a Flash Game
this.y = prevY;
}
}
}
}
shell.x = Math.cos
((turret_mc.rotation+
this.rotation)*Math.PI/180)
shell.y = Math.sin
((turret_mc.rotation+this.
rotation)*Math.PI/180)
tank rotation
(this.rotation in the
scope of PlayerTank)
turret_mc.rotation
(in degrees). To convert
to radians multiply by
Math.PI/180.
gunL ( = gun length)
Figure 6.2
Slightly more trigonometry to account for the tank’s rotation.
Source: © 2013 Keith Gladstien, All Rights Reserved.
Testing version_05 reveals that everything is working. During testing we added trace()
statements to rotateTurretF() and moveTankF() to make sure each loop terminates
when the mouse leaves the tank parent MovieClip and when no keys are pressed,
respectively.
But I have a confession. This is not my version _05. I actually made a different version that
used an enterframeF that called rotateTurretF and moveTankF and one enterFrame loop.
It has always (or for at least as long as I can remember) been my feeling that one
enterFrame loop that called two or more other functions was more efficient than two
or more enterFrame loops each calling one function.
Version _05
As I was starting to explain (for this book) why I made one enterFrame loop to call
rotateTurretF and moveTankF, I thought I should run a test to confirm that feeling.
When I did, I found that one enterFrame loop calling many functions was slower
than many enterFrame loops each calling one function. (The test code is in
enterframe_test_one_v_many_loops_with_one_mc, if you want to check yourself.)
But, they are close to the same speedwise for the number of functions realistically
encountered. Also, using different loops is often easier to code, and fewer variables
are needed because when you’re using one main loop, that loop needs to use Booleans to determine whether to call the other functions and to determine when to
remove that main enterFrame loop.
Note, this tests one versus many enterFrame loops applied to one MovieClip. It doesn’t
test whether it is more efficient for each MovieClip (in this context, tank) to have its
own loops (as currently set up) or whether it is more efficient to have one master
MovieClip with its own loops controlling all the tanks. In the next chapter, I will
address that.
It’s time for the next version. We want to add two more features to PlayerTank
(a shot sound and a moving shell) and also a combat view with a PlayerTank and an
EnemyTank instance.
To start that, we need to edit Main so it removes introView and adds a combat view.
Before Main can add a combat view, we’ll make that barebones MovieClip symbol
in Flash.
We’ll add an arena_mc (similar to playerBG_mc in the intro view library symbol) that
will be used to establish tank (and shell boundaries). We want to leave room for a
timer and a stats display.
We’ll assign it the CombatView class, which we’ll start coding by copying the IntroView
code, editing out the unneeded listeners, and adding the code needed for a PlayerTank
instance and an EnemyTank instance.
Then we need an EnemyTank class, which we’ll start coding by copying the PlayerTank
code and editing out everything unnecessary, such as the keyboard event listeners.
We’ll add some code to color these tanks so they look different from the PlayerTank
instances.
At this point, we should use the _tank mc symbol for both PlayerTank and EnemyTank.
So, we’ll change the _tank mc class (double-click in the AS Linkage column next to
_tank mc) and assign it the com.kglad.Tank class.
125
126
Chapter 6 n Developing a Flash Game
Now we can use that symbol for PlayerTank and EnemyTank by having PlayerTank and
EnemyTank extend the Tank class. And, we’re set up to actually create that class code
and eliminate the duplicate code in PlayerTank and EnemyTank.
I have updated the FLA to tank_combat_06.fla; changed _06 versions of
IntroView, PlayerTank; and created new CombatView, EnemyTank, and Tank classes.
Main,
Version _06
Version _06 Main.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
public class Main extends MovieClip {
// I removed the currentView variable because I
// have different listeners (that need to be removed)
// for the different views. So, I decided it was
// easier to follow and maintain this code by
// explicitly tracking each view.
private var introView:IntroView;
private var combatView:CombatView;
public function Main() {
addIntroViewF();
}
private function addIntroViewF():void{
introView = new IntroView();
// I changed startgameE and startgameF to
// startCombatE and addCombatViewF, resp,
// to make it easier for me to see what they
// do. They are just names so I can use almost
// anything I want. Picking good names can make
// understanding your code much easier not only
// for others (which, except for writing this
// book, has never been a factor for me), but
// also for yourself, and that has always been
// an issue for me. It is not unusual for me to
// edit something I have not looked at for months
// or even years.
introView.addEventListener("startCombatE",addCombatViewF);
addChild(introView);
}
private function addCombatViewF(e:Event):void{
// Ready introView for gc
Version _06
introView.removeEventListener("startCombatE",addCombatViewF);
removeChild(introView);
introView = null;
// Add a CombatView instance
combatView = new CombatView();
combatView.addEventListener("gameOverE",addGameOverViewF);
addChild(combatView);
}
private function addGameOverViewF(e:Event):void{
combatView.removeEventListener("gameOverE",addGameOverViewF);
removeChild(combatView);
combatView = null;
// To be done
trace("gameover", "Main");
}
}
}
The only thing changed in IntroView is some renaming of
and changing the dispatchEvent event name:
startgame
to
dispatchEvent(new Event("startCombatE"));
Version _06 CombatView.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
public class CombatView extends MovieClip {
private var player:PlayerTank;
// In case I want to add more than 1 enemy tank
private var enemyNum:int = 1;
// If you use, var enemyA:Array here and fail to
// create an array instance (using brackets or
// the new Array() constructor), you will
// trigger a 1009 error when you try to apply
// any array property or method to enemyA
private var enemyA:Array = [];
public function CombatView() {
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Define all the click listeners.
listenersF();
// Stub in the code for a stats display
statsDisplayF();
startCombat
127
128
Chapter 6 n Developing a Flash Game
// Add player tank and enemy tank(s)
addTanksF();
}
private function listenersF():void{
// To be done
}
private function statsDisplayF():void{
// To be done
}
private function addTanksF():void{
var w:int = arena_mc.width;
var h:int = arena_mc.height
// create a player instance passing w and h
// used to restrict player’s movement
// (in PlayerTank)
player = new PlayerTank(w,h);
// Position player using arena_mc as the tank parent
positionF(arena_mc,player);
// Add enemy tank(s)
for(var i:int=0;i<enemyNum;i++){
var enemy:EnemyTank = new EnemyTank(w,h);
// Position enemy tank(s)
positionF(arena_mc,enemy);
enemyA.push(enemy);
}
}
private function positionF(parent_mc:MovieClip,mc:MovieClip):void {
parent_mc.addChild(mc);
// I want a little distance between tanks and
// the arena edges
mc.x = int(mc.width+(parent_mc.width-2*mc.width)*Math.random());
mc.y = int(mc.height+(parent_mc.height-2*mc.height)*Math.random());
// If this is an enemy tank make sure it’s not
// hitting another tank. player is added first so
// no need to check for a hit when it’s added
if(mc!=player){
// check if mc is hitting player
if(player.hitTestObject(mc)){
// If there’s a positive hit test, call
// positionF() again and (almost
// certainly) try another position
positionF(parent_mc,mc);
} else {
// check mc doesn’t hit any enemyA
Version _06
// element. Notice mc is not in
// enemyA, yet.
for(var i:int=0;i<enemyA.length;i++){
if(mc.hitTestObject(enemyA[i])){
positionF(parent_mc,mc);
// There is no need to check for
// another hit so terminate this
// for loop.
break;
}
}
}
}
}
}
}
Version _06 Tank.as
package com.kglad {
import flash.display.MovieClip;
import flash.media.Sound;
import flash.events.Event;
import flash.events.MouseEvent;
public class Tank extends MovieClip{
private var xLimit:int;
private var yLimit:int;
// I need to liberalize from private or will get
// an 1178 error: Attempted access of inaccessible
// property prevX through a reference with static
// type com.kglad:PlayerTank.
internal var prevX:Number;
internal var prevY:Number;
private var boundaryViolation:Boolean;
private var shellA:Array = [];
private var gunL:Number;
private var shellSpeed:int = 10;
private var shotSound:Sound;
public function Tank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,initT);
}
private function initT(e:Event):void{
prevX = this.x;
129
130
Chapter 6 n Developing a Flash Game
prevY = this.y;
gunL = this.turret_mc.width-this.turret_mc.top_mc.width/2;
}
// I want to call shootF from my PlayerTank and
// EnemyTank subclasses so I can use the protected
// or any attribute other than private.
protected function shootF(e:MouseEvent):void{
// create a shot sound. I could use
// var shotSound:ShotSound = new ShotSound()
// here but then I would be creating many
// shotSound variables. They are local to
// shootF() so they would be readily gc’d but it
// is more efficient to create one variable and
// reuse it. Actually, it would be even more
// efficient to create one variable and one
// ShotSound(), so in the next version
// I’ll clean that up.
shotSound = new ShotSound();
shotSound.play();
var shell:Shell = new Shell();
shellA.push(shell);
this.parent.addChild(shell);
// shells are no longer stationary. I will need
// to know where each shell should be after I
// shoot it. I will use these parameters,
// cos,sin,tankX,tankY and f.
// I only need each shell’s angle, but rather
// than repeatedly calculate the cosine and
// sine of that angle, I can just save
// those values as parameters and save some
// cpu cycles.
shell.cos =
Math.cos((this.turret_mc.rotation+this.rotation)*Math.PI/180);
shell.sin =
Math.sin((this.turret_mc.rotation+this.rotation)*Math.PI/180);
// shell’s initial position
shell.tankX = this.x;
shell.tankY = this.y;
// I am going to increment f (the distance traveled
// by the shell) by shellSpeed in each loop to update
// the distance traveled by the shell. Initially the
// shell’s distance from this.x,this.y is gunL
shell.f = gunL;
// I may as well use the parameters here to ease my coding.
Version _06
shell.x = this.x+gunL*shell.cos;
shell.y = this.y+gunL*shell.sin;
// Add an enterframe loop to update where each shell
// should be. No reason to keep adding it if it’s
// already been added.
if(shellA.length==1){
// And I should remove this listener when
// shellA.length is zero. I’ll fix that
// in the next version.
this.addEventListener(Event.ENTER_FRAME,shellLoopF);
}
}
private function shellLoopF(e:Event):void{
// Always loop through arrays from end to start
// IF you might be removing array elements. Otherwise,
// you skip an array element every time an element
// is removed.
for(var i:int=shellA.length-1;i>=0;i– –){
// Update the distance the shell has traveled
shellA[i].f+=shellSpeed;
// Assign its x,y using the parameters
// defined in shootF()
shellA[i].x = shellA[i].tankX+shellA[i].f*shellA[i].cos;
shellA[i].y = shellA[i].tankY+shellA[i].f*shellA[i].sin;
// Check if the shell should be removed
shellRemoveCheckF(shellA[i],i);
}
}
private function shellRemoveCheckF(shell:Shell,i:int):void{
// I created a boundaryViolationF() function to
// use for the tank and shells so I don’t have
// to rewrite the same code.
if(boundaryViolationF(shell)){
// ready shellA[i] for gc
this.parent.removeChild(shellA[i]);
// splice(i,1) is an array method that
// removes the ith element when the second
// parameter is one. And here is where you
// would encounter a problem if you were
// looping from the start to the end of an
// array. After removing the ith element,
// what was the i+1 element is now the ith
// element. When your for-loop counter
131
132
Chapter 6 n Developing a Flash Game
// increments, it skips the old i+1 element
// that is now the ith element.
// That might only be inefficient, but it
// can be disastrous.
shellA.splice(i,1);
}
}
protected function moveLimitsF():void{
if(boundaryViolationF(this)){
this.x = prevX;
this.y = prevY;
}
}
Private function boundaryViolationF(mc:MovieClip):Boolean{
boundaryViolation = false;
if(mc.x<mc.width/2){
boundaryViolation = true;
} else if(mc.x>xLimit-mc.width/2){
boundaryViolation = true;
} else if(mc.y<mc.height/2){
boundaryViolation = true;
} else if(mc.y>yLimit-mc.height/2){
boundaryViolation = true;
}
return boundaryViolation;
}
}
}
Version _06 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Mouse;
public class PlayerTank extends Tank {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
// These are already declared in the Tank class. As long
// as I use an attribute (ie, anything but protected) in
// the Tank class that allows these and other variables
Version _06
// to be accessible to subclasses, I do not need to declare
// them here.
//private var prevX:Number;
//private var prevY:Number;
private var keyCodeIndex:int;
private var directionA:Array = [];
public function PlayerTank(_xLimit:int,_yLimit:int) {
// You need to call the superclass constructor (Tank)
// or you’ll trigger a 1203 error: No default
// constructor found in base class
// com.kglad:Tank error.
super(_xLimit,_yLimit);
cCursor = new CustomCursor();
this.addEventListener(Event.ADDED_TO_STAGE,initP);
}
private function initP(e:Event):void{
this.parent.addEventListener(MouseEvent.MOUSE_DOWN,shootF);
this.parent.addEventListener(MouseEvent.ROLL_OVER,
addCustomCursorF);
this.parent.addEventListener(MouseEvent.ROLL_OUT,
removeCustomCursorF);
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.addEventListener(KeyboardEvent.KEY_UP,keyupF);
}
private function addCustomCursorF(e:MouseEvent):void{
// no change
}
private function removeCustomCursorF(e:MouseEvent):void{
// no change
}
private function rotateTurretF(e:Event):void{
// no change
}
private function keydownF(e:KeyboardEvent):void{
// no change
}
private function keyupF(e:KeyboardEvent):void{
// no change
}
private function moveTankF(e:Event):void{
// no change
}
}
}
133
134
Chapter 6 n Developing a Flash Game
Version _06 EnemyTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Mouse;
import flash.geom.ColorTransform;
import flash.media.Sound;
public class EnemyTank extends Tank {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
// Everything else declared in Tank Class
public function EnemyTank(_xLimit:int,_yLimit:int) {
// Call the Tank constructor
super(_xLimit,_yLimit);
// color this enemy
colorF();
}
private function colorF():void{
// Use the color propery of the ColorTransform
// class to assign colors to display objects.
var ct:ColorTransform = new ColorTransform();
ct.color = 0x000099;
this.base_mc.transform.colorTransform = ct;
ct.color = 0x000066;
this.turret_mc.top_mc.transform.colorTransform = ct;
}
private function rotateTurretF(e:Event):void{
// To be done: Rotate the turret
}
private function moveTankF(e:Event):void{
// To be done: Update tank position, rotation.
moveLimitsF();
//prevX = this.x;
//prevY = this.y;
}
}
}
Version _07
After testing, we can see this works pretty well, except that we have to click on the
combat view stage to start detecting key presses. We added a trace() to PlayerTank’s
keydownF and found two traces for each key press. That can occur only if we have two
keydown listeners.
I was going to address readying objects for gc later, but I cannot put that off any
longer. We have been sloppy about creating objects and not readying them for gc
(garbage collection) when they’re no longer needed. This is a good time to do that
because we’re already seeing a problem: We have created, among other problematic
things, two different stage keydown listeners, and they are both calling our combat
view PlayerTank instance’s keydownF. That is, each keypress results in two calls to
keydownF.
Further, our sloppiness caused the combat view problem that required the stage
to be clicked for those listeners to work. Using trace(stage.focus) in Main’s
AddCombatViewF(), you found that when you clicked the startCombat_mc, focus was
changed to startCombat_mc. But then, startCombat_mc’s parent was removed from the
display list (which removed startCombat_mc from the display list), so nothing related
to the stage had focus.
We could remedy the stage focus problem by assigning the stage.focus property, but
it’s better to clean up everything in case more problems are lurking.
In addition to those changes in the next version, we added movement and shooting
code to EnemyTank. There were no changes to the FLA, Main, or KBcontrols.
Version _07
Version _07 IntroView.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import flash.events.Event;
public class IntroView extends MovieClip {
private var player:PlayerTank;
public function IntroView() {
this.addEventListener(Event.ADDED_TO_STAGE,init);
// Clean up everything in this class when
// no longer needed. That is, when
// removeChild(introView) executes in Main,
// the Event.REMOVED_FROM_STAGE event will
// be dispatched and removeF() will be called.
135
136
Chapter 6 n Developing a Flash Game
this.addEventListener(Event.REMOVED_FROM_STAGE,removeF);
}
private function init(e:Event):void{
// I may as well remove this listener now. It
// is not needed after detecting the
// Event.ADDED_TO_STAGE event once.
this.removeEventListener(Event.ADDED_TO_STAGE,init);
listenersF();
addPlayerTankF();
}
private function removeF(e:Event):void{
this.removeEventListener(Event.REMOVED_FROM_STAGE,removeF);
// Remove all the listeners created in IntroView
removeListenersF();
// Remove everything including that problematic
// startCombat_mc button that had focus and was
// then removed from the stage.
removeAllF();
}
private function listenersF():void{
left_mc.addEventListener(MouseEvent.CLICK,leftF);
left_mc.buttonMode = true;
right_mc.addEventListener(MouseEvent.CLICK,rightF);
right_mc.buttonMode = true;
forward_mc.addEventListener(MouseEvent.CLICK,forwardF);
forward_mc.buttonMode = true;
back_mc.addEventListener(MouseEvent.CLICK,backF);
back_mc.buttonMode = true;
startCombat_mc.addEventListener(MouseEvent.CLICK,startCombatF);
startCombat_mc.buttonMode = true;
}
private function removeListenersF():void{
left_mc.removeEventListener(MouseEvent.CLICK,leftF);
right_mc.removeEventListener(MouseEvent.CLICK,rightF);
forward_mc.removeEventListener(MouseEvent.CLICK,forwardF);
back_mc.removeEventListener(MouseEvent.CLICK,backF);
startCombat_mc.removeEventListener(MouseEvent.CLICK,startCombatF);
}
private function removeAllF():void{
// This is one way to remove all children of a
// DisplayObjectContainer. Another is to loop
// through the depths backward, similar to
// removing elements from an array.
for(var i:int=0;i<this.numChildren;i++){
Version _07
removeChildAt(0);
}
}
// The rest of this class is unchanged.
}
}
Version _07 CombatView.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
public class CombatView extends MovieClip {
private var player:PlayerTank;
// In case I want to add more than 1 enemy tank
private var enemyNum:int = 1;
// If you use, var enemyA:Array here and fail to
// create an array instance, var enemyA:Array = []
// or var enemyA:Array = new Array(),
// you will get a 1009 error when you try to apply
// any array property or method to enemyA
private var enemyA:Array = [];
var hitBool:Boolean;
public function CombatView() {
this.addEventListener(Event.ADDED_TO_STAGE,init);
// Again, the Event.REMOVED_FROM_STAGE
// event listener to use for clean up
this.addEventListener(Event.REMOVED_FROM_STAGE,removedF);
}
private function init(e:Event):void{
// Again, remove the Event.ADDED_TO_STAGE
// listener as soon as it has completed
// its only job.
this.removeEventListener(Event.ADDED_TO_STAGE,init);
// define all the click listeners
listenersF();
statsDisplayF();
// Add player tank and enemy tank(s)
addTanksF();
}
private function listenersF():void{
// To be done
}
private function statsDisplayF():void{
// To be done
137
138
Chapter 6 n Developing a Flash Game
}
private function addTanksF():void{
var w:int = arena_mc.width;
var h:int = arena_mc.height
player = new PlayerTank(w,h);
positionF(arena_mc,player);
// Add enemy tank(s)
for(var i:int=0;i<enemyNum;i++){
// Pass a reference to this class
// instance so enemy can access
// player. See playerT() getter below.
var enemy:EnemyTank = new EnemyTank(this,w,h);
// Position enemy tank(s)
positionF(arena_mc,enemy);
enemyA.push(enemy);
}
}
private function positionF(arena_mc:MovieClip,mc:MovieClip):void {
// no change
}
// Allow access to player in EnemyTank to determine
// player’s position and, in a later version,
// possible shell trajectories for evasive
// manuevers.
public function get playerT():PlayerTank{
return player;
}
// Remove the remaining listener and added displayobjects
private function removedF(e:Event):void{
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
for(var i:int=0;i<arena_mc.numChildren;i++){
arena_mc.removeChildAt(0);
}
}
}
}
Version _07 Tank.as
package com.kglad {
import flash.display.MovieClip;
import flash.media.Sound;
import flash.events.Event;
import flash.events.MouseEvent;
public class Tank extends MovieClip{
Version _07
private var xLimit:int;
private var yLimit:int;
protected var prevX:Number;
protected var prevY:Number;
private var shellA:Array = [];
private var gunL:Number;
private var shellSpeed:int = 10;
// Here’s the more efficient code for shotSound.
// I can apply the play() method to shotSound
// repeatedly for each shot
private var shotSound:Sound = new ShotSound();
// I decided to limit the maximum number of
// shells that can exist on-screen at any one
// time so this is not a blast-fest.
private var maxShots:int = 1;
public function Tank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,initT);
// Clean up
this.addEventListener(Event.REMOVED_FROM_STAGE, removedF);
}
private function initT(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,initT);
prevX = this.x;
prevY = this.y;
gunL = this.turret_mc.width-this.turret_mc.top_mc.width/2;
}
protected function shootF(e:MouseEvent):void{
// Here’s where I limit the number of shells
// that can exist at any one time
if(shellA.length<maxShots){
// Here’s the more efficient code at work
// using the same one variable referencing
// the same sound instance
shotSound.play();
var shell:Shell = new Shell();
shellA.push(shell);
this.parent.addChild(shell);
shell.cos =
Math.cos((this.turret_mc.rotation+this.rotation)*Math.PI/180);
shell.sin =
Math.sin((this.turret_mc.rotation+this.rotation)*Math.PI/180);
shell.tankX = this.x;
139
140
Chapter 6 n Developing a Flash Game
shell.tankY = this.y;
shell.f = gunL;
shell.x = this.x+gunL*shell.cos;
shell.y = this.y+gunL*shell.sin;
if(shellA.length==1){
this.addEventListener(Event.ENTER_FRAME,shellLoopF);
}
}
}
private function shellLoopF(e:Event):void{
for(var i:int=shellA.length-1;i>=0;i– –){
shellA[i].f+=shellSpeed;
shellA[i].x = shellA[i].tankX+shellA[i].f*shellA[i].cos;
shellA[i].y = shellA[i].tankY+shellA[i].f*shellA[i].sin;
shellRemoveCheckF(shellA[i],i);
}
}
private function shellRemoveCheckF(shell:Shell,i:int):void{
if(boundaryViolationF(shell)){
this.parent.removeChild(shellA[i]);
shellA.splice(i,1);
}
// Remove enterframe loop if no more shells
if(shellA.length==0){
this.removeEventListener(Event.ENTER_FRAME,shellLoopF);
}
}
protected function moveLimitsF():void{
if(boundaryViolationF(this)){
this.x = prevX;
this.y = prevY;
}
}
protected function boundaryViolationF(mc:MovieClip):String{
// I made some changes here so I could
// determine which boundary was violated.
// I thought that might be needed so EnemyTank
// instances could change direction
// intelligently when a boundary was violated.
// But, so far, that hasn’t been needed.
if(mc.x<mc.width/2){
return "L";
} else if(mc.x>xLimit-mc.width/2){
Version _07
return "R";
} else if(mc.y<mc.height/2){
return "T";
} else if(mc.y>yLimit-mc.height/2){
return "B";
}
return "";
}
// Only shell instances might need to be removed.
private function removedF(e:Event):void{
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
if(shellA.length>0){
// if shellA.length==0, this loop has
// already been removed
this.removeEventListener(Event.ENTER_FRAME,shellLoopF);
for(var i:int=shellA.length-1;i>=0;i– –){
if(shellA[i].parent){
shellA[i].parent.removeChildAt(0);
}
}
}
}
}
}
Version _07 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Mouse;
public class PlayerTank extends Tank {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var keyCodeIndex:int;
private var directionA:Array = [];
private var cCursor:CustomCursor;
public function PlayerTank(_xLimit:int,_yLimit:int) {
super(_xLimit,_yLimit);
cCursor = new CustomCursor();
this.addEventListener(Event.ADDED_TO_STAGE,initP);
141
142
Chapter 6 n Developing a Flash Game
// The usual listener for clean-up purposes.
this.addEventListener(Event.REMOVED_FROM_STAGE,removedP);
}
private function initP(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,initP);
this.parent.addEventListener(MouseEvent.MOUSE_DOWN,shootF);
this.parent.addEventListener(MouseEvent.ROLL_OVER,
addCustomCursorF);
this.parent.addEventListener(MouseEvent.ROLL_OUT,
removeCustomCursorF);
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.addEventListener(KeyboardEvent.KEY_UP,keyupF);
}
private function addCustomCursorF(e:MouseEvent):void{
// no change
}
private function removeCustomCursorF(e:MouseEvent):void{
// no change
}
private function rotateTurretF(e:Event):void{
// no change
}
private function keydownF(e:KeyboardEvent):void{
// no change
}
private function keyupF(e:KeyboardEvent):void{
// no change
}
private function moveTankF(e:Event):void{
// no change except no need to call
// rotateTurret(e) from here
// rotateTurretF(e);
}
private function removedP(e:Event):void{
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedP);
this.parent.removeEventListener(MouseEvent.MOUSE_DOWN,shootF);
this.parent.removeEventListener(MouseEvent.ROLL_OVER,
addCustomCursorF);
this.parent.removeEventListener(MouseEvent.ROLL_OUT,
removeCustomCursorF);
stage.removeEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.removeEventListener(KeyboardEvent.KEY_UP,keyupF);
this.removeEventListener(Event.ENTER_FRAME,moveTankF);
Version _07
this.removeEventListener(Event.ENTER_FRAME,rotateTurretF);
}
}
}
Version _07 EnemyTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.utils.Timer;
import flash.utils.getTimer;
import flash.events.TimerEvent;
public class EnemyTank extends Tank {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
// Used to access player so enemy tank knows where to shoot
private var cv:CombatView;
// Current direction in degrees (0 to 359). When direction
// changes, update distance to speed and startX,startY to
// current x,y
private var currentDirection:int;
// Next direction in degrees. Must gradually change
// currentDirection to nextDirection.
private var nextDirection:int;
// These next three variables are used in tank movement.
private var distance:int;
private var startX:int;
private var startY:int;
// Frequency of random direction changes (evasive
// manuevering) in ms
private var directionChangeFreq:int = 2500;
// This is a degrees to radians constant because I finally
// got tired of typing the same thing repeatedly. I should
// actually define this in Tank and use it in Tank, PlayerTank
// and EnemyTank. That will be in the next update.
private const d2r:Number = Math.PI/180;
// Timer for direction changes
private var directionChangeTimer:Timer;
// Boolean used to indicate whether this tank is moving
// backwards. (Used when there is a boundary violation.)
143
144
Chapter 6 n Developing a Flash Game
private var moveBackBool:Boolean;
// Timer used to stop moving backwards from a boundary.
// That is, when a boundary is encountered, an EnemyTank
// instance will move backwards without changing direction
// until moveBackTimer dispatches a TimerEvent.TIMER event.
private var moveBackTimer:Timer;
public function EnemyTank(_cv:CombatView,_xLimit:int,_yLimit:int) {
super(_xLimit,_yLimit);
colorF();
cv = _cv;
// Initialize currentDirection
currentDirection = int(360*Math.random());
// Initialize distance
distance = speed;
this.addEventListener(Event.ADDED_TO_STAGE,init);
this.addEventListener(Event.REMOVED_FROM_STAGE,removedF);
}
private function init(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
// Start loops for tank movement and
// turret rotation
this.addEventListener(Event.ENTER_FRAME,moveTankF);
this.addEventListener(Event.ENTER_FRAME,rotateTurretF);
// Timer used to change directions (and
// make this tank’s position more difficult
// to predict and thereby more difficult to
// shoot).
directionChangeTimer = new Timer(directionChangeFreq,0);
directionChangeTimer.addEventListener(TimerEvent.TIMER,newDirectionF);
directionChangeTimer.start();
// Timer to terminate moving backward after
// boundary violation
moveBackTimer = new Timer(1000,1);
moveBackTimer.addEventListener(TimerEvent.TIMER,moveBackF);
}
private function newDirectionF(e:TimerEvent):void{
// ai: turn towards stage center. Determine
// which quadrant the tank is currently located
// and then assign nextDirection to be towards
// stage-center. That should help prevent this
// tank from getting trapped in a corner.
var quadrant:String;
if(this.x<this.parent.width/2){
quadrant="L";
Version _07
} else {
quadrant="R";
}
if(this.y<this.parent.height/2){
quadrant+="U";
} else {
quadrant+="D";
}
if(quadrant=="LU"){
nextDirection = int(90*Math.random());
} else if(quadrant=="LD"){
nextDirection = 270+int(90*Math.random());
} else if(quadrant=="RU"){
nextDirection = 90+int(90*Math.random());
} else {
nextDirection = 180+int(90*Math.random());
}
// start loop to change from currentDirection
// to nextDirection
this.addEventListener(Event.ENTER_FRAME,directionChangeF);
}
private function colorF():void{
var ct:ColorTransform = new ColorTransform();
ct.color = 0x0000cc;
this.base_mc.transform.colorTransform = ct;
ct.color = 0x000066;
this.turret_mc.top_mc.transform.colorTransform = ct;
}
private function rotateTurretF(e:Event):void{
// Aim towards player (retrieved via CombatView
// instance cv’s playerT getter)
shootF(null);
// I want this.turret_mc.rotataion+this.rotation
// to be equal to the angle from this to cv.playerT.
// The angle from this EnemyTank instance to
// cv.playerT is easily determined just like
// previously discussed in chapter 4 using
// Math.atan2().
this.turret_mc.rotation =
-this.rotation+180*Math.atan2(cv.playerT.y-this.y,cv.playerT.x-this.x)/Math.PI;
}
private function moveTankF(e:Event):void{
// Initialize startX,startY. Note this cannot
145
146
Chapter 6 n Developing a Flash Game
// be done in init() because init() is called
// when this tank is added to the display list
// and before its x,y are reset from 0,0 in
// positionF(), whereas the first enterframe loop
// always occurs some milliseconds after it’s
// defined (and after this tank is positioned).
if(startX==0){
startX = this.x;
startY = this.y;
}
// If moving backwards don’t rotate towards
// direction of movement. Otherwise, rotate
// to movement direction.
if(!moveBackBool){
this.rotation = currentDirection;
}
// Update position based on the variables
// startX,startY,distance and currentDirection.
this.x = startX+distance*Math.cos(currentDirection*d2r);
this.y = startY+distance*Math.sin(currentDirection*d2r);
// Update distance
distance += speed;
moveLimitsF();
prevX = this.x;
prevY = this.y;
}
// Enemy tanks need more control over boundary
// behavior than I could conveniently achieve in
// Tank, so I’m overriding Tank’s moveLimitsF()
// with the moveLimitsF() here. I need to use the
// override attribute to inform Flash that this
// moveLimitsF() will execute and not the one in
// Tank.
override protected function moveLimitsF():void{
if(boundaryViolationF(this)){
// Move to position prior to
// boundary violation.
this.x = prevX;
this.y = prevY;
// Get ready to move in opposite
// direction.
// Stop the evasive manuevering.
// Must get off the boundary asap or this tank is doomed.
directionChangeTimer.stop();
Version _07
// Reset distance, startX and startY
directionChangeUpdateF();
// Change currentDirection by 180
// degrees. I modulo all my angles
// by 360 to make it easy for me to
// deal with these directions.
currentDirection = (180+currentDirection)%360;
// nextDirection will be (minus) 90
// degrees to the direction that led
// to the boundary violation.
nextDirection = (90+currentDirection)%360;
// Enable moveBackBool so this tank looks
// like it’s moving backwards and not
// turning away from the boundary (yet).
moveBackBool = true;
// Start the moveBackTimer to stop this
// tank moving backward and start it
// moving forward and turning towards
// nextDirection.
moveBackTimer.start();
// Stop any direction change activity.
// This tank won’t be ready to change
// direction until we complete backing
// away from boundary.
this.removeEventListener(Event.ENTER_FRAME,directionChangeF);
}
}
// This function is called when this tank is done
// moving backwards from a boundary.
private function moveBackF(e:TimerEvent):void{
// Reset those parameters.
directionChangeUpdateF();
// No longer will this tank move backwards
// so we can face the direction of movement
moveBackBool = false;
// Rechange currentDirection back towards the
// boundary (but nextDirection is set to turn
// -90 degrees so hopefully we won’t
// hit the boundary again).
currentDirection = (180+currentDirection)%360;
// Restart evasive manuever intervals.
directionChangeTimer.start();
// Start the loop to gradually change direction
147
148
Chapter 6 n Developing a Flash Game
// from currentDirection to nextDirection so this
// tank smoothly turns away from the boundary.
this.addEventListener(Event.ENTER_FRAME,directionChangeF);
}
// Here’s where direction changes are made from
// currentDirection to nextDirection.
private function directionChangeF(e:Event):void{
// transition from currentDirection to nextDirection
// the "short" way (explained below).
if(currentDirection<=nextDirection){
if(nextDirection-currentDirection<=180){
// increment currentDirection
currentDirection+=rotationRate;
} else {
// decrement currentDirection. (eg,
// currentDirection=5 & nextDirection=355,
// go the "short" way, 5 to 355
currentDirection = (currentDirectionrotationRate+360)%360;
}
} else {
// currentDirection>nextDirection
if(currentDirection-nextDirection<180){
// decrement currentDirection
currentDirection-=rotationRate;
} else {
// increment currentDirection
currentDirection =
(currentDirection+rotationRate)%360;
}
}
// If currentDirection and nextDirection are close,
// stop the direction change loop
if(Math.abs(currentDirection-nextDirection)<=rotationRate/2){
this.removeEventListener(Event.ENTER_FRAME,directionChangeF);
}
// Reset parameters with each direction change.
// The same parameters are valid and don’t need
// to be changed (with distance being incremented
// by speed) while moving in a straight line.
directionChangeUpdateF()
}
Version _08
private function directionChangeUpdateF():void{
distance = speed;
startX = this.x;
startY = this.y;
}
private function removedF(e:Event):void{
// Clean up
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
this.removeEventListener(Event.ENTER_FRAME,moveTankF);
this.removeEventListener(Event.ENTER_FRAME,rotateTurretF);
this.removeEventListener(Event.ENTER_FRAME,directionChangeF);
directionChangeTimer.removeEventListener(TimerEvent.TIMER,newDirectionF);
moveBackTimer.removeEventListener(TimerEvent.TIMER,moveBackF);
}
}
}
And, test.
This works pretty well. There’s a problem with the enemy tank shots not being perfect, likely caused by shooting and then updating the aim point instead of updating
the aim point and then shooting. That is easily fixed.
And, while I’m still not happy with the boundary behavior, it is now rare for an
enemy tank to violate a boundary because of the turn toward stage-center coding.
In fact, I like results of the turn toward stage-center so much, I think we should institute that for boundary violations. That should almost eliminate any chance of an
EnemyTank instance getting trapped in a corner.
In addition, we’ll add tank-versus-tank collision detection and shell-versus-tank collision dectection. Tank versus tank will result in the destruction of both tanks, and
shells will do damage to tanks until a maxHits parameter is reached, triggering tank
destruction. We’ll add a DestroySound sound and Explosion MovieClip to play when a
tank is destroyed.
We’ll also add the stats display and the game-over view. That will require updates to
the FLA and every class except KBcontrol and IntroView.
Version _08
Version _08 Main.as
package com.kglad {
import flash.display.MovieClip;
149
150
Chapter 6 n Developing a Flash Game
import flash.events.Event;
public class Main extends MovieClip {
private var introView:IntroView;
private var combatView:CombatView;
private var gameOverView:GameOverView;
public function Main() {
addIntroViewF();
}
private function addIntroViewF():void{
introView = new IntroView();
introView.addEventListener("startCombatE",addCombatViewF);
addChild(introView);
}
private function removeIntroViewF():void{
// Ready intoView for gc
introView.removeEventListener("startCombatE",addCombatViewF);
removeChild(introView);
introView = null;
}
private function addCombatViewF(e:Event):void{
removeIntroViewF();
// Add a CombatView instance
combatView = new CombatView();
combatView.addEventListener("gameOverE",addGameOverViewF);
addChild(combatView);
}
private function removeCombatViewF():void{
// Ready combatView for gc
combatView.removeEventListener("gameOverE",addGameOverViewF);
removeChild(combatView);
combatView = null;
}
private function addGameOverViewF(e:Event):void{
// I wanted to get this player reference
// before losing my reference to combatView,
// so I could pass it to the GameOverView
// instance. Then I could display player stats
// in the GameOverView instance.
var player:PlayerTank = combatView.playerT;
removeCombatViewF();
// Add a GameOverView instance
gameOverView = new GameOverView(player);
// Add a listener for the replay button
gameOverView.addEventListener("replayE",replayF);
Version _08
addChild(gameOverView);
}
private function removeGameOverViewF():void{
// Ready gameOverView for gc
gameOverView.removeEventListener("replayE",replayF);
removeChild(gameOverView);
gameOverView = null;
}
private function replayF(e:Event):void{
removeGameOverViewF();
// And add an IntroView instance. The customized
// keys do not need to be redefined.
addIntroViewF();
}
}
}
Version _08 CombatView.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class CombatView extends MovieClip {
private var player:PlayerTank;
// Option to control the number of enemy tanks
private var enemyNum:int = 11;
private var enemyA:Array = [];
// maxHits defined in Tank and passed from Tank
private var maxHits:int;
// Time in ms to delay removing this view and
// adding game over view
private var timeTilGameOverView:int = 3000;
public function CombatView() {
this.addEventListener(Event.ADDED_TO_STAGE,init);
this.addEventListener(Event.REMOVED_FROM_STAGE,removedF);
}
private function init(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
statsDisplayF();
// Add player tank and enemy tank(s)
addTanksF();
}
private function statsDisplayF():void{
151
152
Chapter 6 n Developing a Flash Game
// Initialize the display of player score
// and player life
score_tf.text = "0";
life_tf.text = "100%";
}
private function addTanksF():void{
var w:int = arena_mc.width;
var h:int = arena_mc.height
player = new PlayerTank(w,h,this);
// I needed Tank instance names to distinguish
// one EnemyTank from another so I may as well
// assign a name property to the PlayerTank
// instance and use it. I could distinguish the
// PlayerTank instance by using if(tank is
// PlayerTank) but there was no way to distinguish
// one EnemyTank from another EnemyTank by checking
// Class membership.
player.name = "player";
// Create arrays for each Tank instance to store
// their foes (foeA) and friends (friendA).
// Arrays created in Tank. Used to determine who
// to shoot and who to not shoot for fear
// of hitting a friend. Some of that is done in
// Tank but, as yet, there is no code to withdraw
// a shot that is apt to hit a friend.
positionF(arena_mc,player);
// Add enemy tank(s)
for(var i:int=0;i<enemyNum;i++){
// Pass a reference to "this" instance so
// EnemyTank instances have access to the
// getter below
var enemy:EnemyTank = new EnemyTank(w,h,this);
// The name property is a string so I have
// to coerce the int i to a string. You can
// also use String(i).
enemy.name = i.toString();
// All enemies have player as a foe. You can
// also add other enemies and have a free-for// all where every Tank instance shoots every
// other Tank instance.
enemy.foeA.push(player);
positionF(arena_mc,enemy);
enemyA.push(enemy);
}
Version _08
// all enemies are player foes.
player.foeA = player.foeA.concat(enemyA);
// player has no friends. So, I did not define a
// player.friendA. You’re on your own. If you want
// a free-for-all (enemies shooting each other and
// player) - uncomment this for loop and comment
// out the next for loop
///*
for(i=0;i<enemyNum;i++){
// Add all enemies to each enemy’s foeA
enemyA[i].foeA = enemyA[i].foeA.concat(enemyA);
// Remove enemyA[i] so no one is their own foe.
enemyA[i].foeA.splice(i+1,1);
}
//*/
// The next for loop makes enemy instances all friends
// of each other. But, if you want a free-for-all, comment
// out the following for loop and uncomment the above for loop.
/*
for(i=0;i<enemyNum;i++){
// add all enemies to each enemy’s friendA
enemyA[i].friendA = enemyA[i].friendA.concat(enemyA);
// remove enemyA[i] so no one is their own friend.
// At this point no one targets friends, but if a
// shell hits a friend, it counts. Later I may add
// code to check that no shot is apt to hit a friend.
enemyA[i].friendA.splice(i,1);
}
*/
}
private function positionF(arena_mc:MovieClip,mc:MovieClip):void {
arena_mc.addChild(mc);
mc.x = int(mc.width+(arena_mc.width-2*mc.width)*Math.random());
mc.y = int(mc.height+(arena_mc.height-2*mc.height)*Math.random());
// If this is an enemy tank make sure it’s not hitting
// another tank. player is added first so no need to
// check for a hit when it’s added.
if(mc!=player){
if(player.hitTestObject(mc)){
positionF(arena_mc,mc);
} else {
// check mc doesn’t hit any enemyA element
for(var i:int=0;i<enemyA.length;i++){
if(mc.hitTestObject(enemyA[i])){
153
154
Chapter 6 n Developing a Flash Game
positionF(arena_mc,mc);
break;
}
}
}
}
}
///// begin data sharing code ////
// Allow Tank to access player
public function get playerT():PlayerTank{
return player;
}
// Pass maxHits from Tank (where it is a parameter) so it
// can be used in the stats display (in playerHitF below)
public function maxHitsF(n:int):void{
maxHits = n;
}
// Pass a combat start delay parameter so the stats display
// (ie, combat countdown) can be updated, warning player how
// long before combat starts. I needed this delay when I
// started to allow multiple enemies. Without it, I (player)
// was getting creamed before I had a chance to move.
public function startTimeF(n:int):void{
// Display time until start
startTime_tf.text = n.toString();
// Create timer to countdown the start time.
var startTimer:Timer = new Timer(1000,n);
startTimer.addEventListener(TimerEvent.TIMER,timerF);
startTimer.start();
}
// Destroyed tank (from Tank) is passed here so it can be
// removed from all arrays: enemyA, player.foeA, all
// enemyA member.foeA and all enemyA member.friendA
public function removeTankF(tank:Tank):void{
// Remove if a player foe.
for(var j2:int=player.foeA.length-1;j2>=0;j2– –){
if(player.foeA[j2].name==tank.name){
player.foeA.splice(j2,1);
break;
}
}
// Remove from enemyA before check enemyA element
// foeA and friendA. No need to look for tank in
// its own foeA or enemyA
Version _08
for(j2=enemyA.length-1;j2>=0;j2– –){
if(enemyA[j2].name==tank.name){
enemyA.splice(j2,1);
break;
}
}
for(j2=enemyA.length-1;j2>=0;j2– –){
// Remove if an enemy foe
for(var j3:int=enemyA[j2].foeA.length-1;j3>=0;j3– –){
if(enemyA[j2].foeA[j3].name==tank.name){
enemyA[j2].foeA.splice(j3,1);
break;
}
}
// Remove if an enemy friend
for(j3=enemyA[j2].friendA.length-1;j3>=0;j3– –){
if(enemyA[j2].friendA[j3].name==tank.name){
enemyA[j2].friendA.splice(j3,1);
break;
}
}
}
}
// score is passed from Tank to update the stats display
public function set score(n:int):void{
// I need to pass something to GameOverView so it
// can display some stats and determine if player
// won or lost. I used a player reference and
// tacked a new property onto player (score), which
// continues my ad-hoc data handling. I used so much
// ad-hoc data handling, without comments, even I
// can’t remember where data is being sent or received.
// I should fix that by using one class for handling data
// like I did with the keyboard control data.
// In fact, that would be a good update. I will rename
// KBcontrol to something clever (like Data) and pass and
// retrieve data that needs to be shared across classes
// via a Data class. But for now, I assigned a property
// to player so that property can be used in GameOverView.
player.score = n;
// Update stats display
score_tf.text = n.toString();
if(enemyA.length == 0){
155
156
Chapter 6 n Developing a Flash Game
// If there are no more enemies, the game is over
// and player won. Well, there’s actually a
// possible bug here (that I will need to fix
// later) because in free-for-all mode, when
// player is dead, EnemyTank instances keep
// shooting each other because I don’t
// instantly close down the CombatView
// instance. (More on that below.) This
// function is where I delay removing this
// view and adding game over view.
delayLeavingCombatViewF();
}
}
// This is called from Tank to update the stats display
public function playerHitF():void{
// This is where maxHits is used to update the
// stats display
life_tf.text = int(100*(maxHits-player.hits)/maxHits)+"%";
if(player.hits==maxHits){
// Game over. player lost.
// Same delay function executes whether
// player wins or loses.
delayLeavingCombatViewF();
}
}
//////// end data sharing code //////
private function timerF(e:TimerEvent):void{
// Update start time countdown
startTime_tf.text =
(e.target.repeatCount-e.target.currentCount).toString();
if(e.target.currentCount==e.target.repeatCount){
// Remove the listener when no longer needed.
e.target.removeEventListener(TimerEvent.TIMER,timerF);
}
}
private function delayLeavingCombatViewF():void{
// I use the timeTilGameOverView variable here and
// call endCombatF() when it is time to remove this
// CombatView
var delayTimer:Timer = new Timer(timeTilGameOverView,1);
delayTimer.addEventListener(TimerEvent.TIMER,endCombatF);
delayTimer.start();
}
// Finally, dispatch the endCombatE event so Main can
Version _08
// remove the CombatView instance and add a GameOverView
// instance.
private function endCombatF(e:Event):void{
e.target.removeEventListener(TimerEvent.TIMER,endCombatF);
dispatchEvent(new Event("endCombatE"));
}
private function removedF(e:Event):void{
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
for(var i:int=0;i<arena_mc.numChildren;i++){
arena_mc.removeChildAt(0);
}
// empty enemyA
enemyA.length = 0;
player = null;
// There should be no more tank references, now
}
}
}
Version _08 GameOverView.as
package com.kglad {
import
import
import
import
import
import
public
flash.display.MovieClip;
flash.events.Event;
flash.events.MouseEvent;
flash.text.TextField;
flash.text.TextFormat;
flash.text.Font;
class GameOverView extends MovieClip {
// I use player to get data from CombatView.
private var player:PlayerTank
public function GameOverView(_player:PlayerTank) {
// This is really a terrible way to pass data.
// The more I see, the more aggravated I get.
// If I use arrows to show where the player
// reference is coming from and going to, it
// would look like:
// CombatView -> Main -> GameOverView
// And some of the data is needed in GameOverView
// and attached to player (player.score) for no
// reason other than to get the data into
// GameOverView and follows this trail:
// Tank -> CombatView
// So, this is confirmation, if any is needed, that
157
158
Chapter 6 n Developing a Flash Game
// data handling needs to be cleaned up. It is too
// convoluted and practically Byzantine.
player = _player;
this.addEventListener(Event.ADDED_TO_STAGE,init);
this.addEventListener(Event.REMOVED_FROM_STAGE,removedF);
listenersF();
}
private function init(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
// Create the start of the string that will be
// displayed in this view’s textfield.
if(player.foeA.length==0){
var s:String = "You won!!\n\nYou had ";
} else {
s = "You lost!\n\nYou had ";
}
// Append the rest of the string. Here is where
// I use player to get data into GameOverView
s+= player.score+" enemy hits\nand you were shot "+player.hits+"
times.";
// I’m going to add a textfield and format its
// text so it uses the same font that’s been
// used in the static text. Consequently, I want
// to embed the font to ensure users see what
// I see and don’t use a system font if they don’t
// have Verdana regular. This is how to embed a font
// for a textfield created with ActionScript.
var tfor:TextFormat = new TextFormat();
// I added Verdana regular font to my library and
// assigned a suggestive class name, Verdana.
// Adding a font to your library is a multistep
// process. Click the upper right of your library
// panel > click "New Font…" > type a name >
// select your preferred Family and Style from the
// combo boxes > check the needed "Character
// ranges" > click ActionScript > check "Export
// for ActionScript" > type a Class name >
// click OK. If the compiler complains about
// trying to access fontName from verdana,
// type it as a Font, not Verdana:
// var verdana:Font = new Verdana();
// or cast verdana as a font:
// Font(verdana).fontName
var verdana:Verdana = new Verdana();
Version _08
// Other than adding a font to the library, this is
// the only non-obvious step. The textformat’s font
// should be assigned the fontName property of your font.
tfor.font = verdana.fontName;
tfor.size = 14;
// Create the textfield
var tf:TextField = new TextField();
// Enable the textfield’s embedFonts property.
tf.embedFonts = true;
// Assign text
tf.text = s;
// If text will be assigned after assigning the
// textformat, use the defaultTextFormat property
// of tf:
// tf.defaultTextFormat = tfor;
// If the text is assigned before the textformat is
// applied, use the setTextFormat() method:
tf.setTextFormat(tfor);
// And, if needed (or you do not understand when to
// use one or the other), you can do both.
tf.multiline = true;
tf.width = 400;
tf.autoSize = "left";
tf.x = (stage.stageWidth-tf.width)/2;
tf.y = 200;
addChild(tf);
}
private function listenersF():void{
replay_mc.addEventListener(MouseEvent.CLICK,replayF);
replay_mc.buttonMode = true;
}
private function removedF(e:Event):void{
for(var i:int=0;i<this.numChildren;i++){
this.removeChildAt(0);
}
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
}
private function replayF(e:Event):void{
replay_mc.removeEventListener(MouseEvent.CLICK,replayF);
dispatchEvent(new Event("replayE"));
}
}
}
159
160
Chapter 6 n Developing a Flash Game
Version _08 Tank.as
package com.kglad {
import flash.display.MovieClip;
import flash.media.Sound;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.getTimer;
public class Tank extends MovieClip{
private var xLimit:int;
private var yLimit:int;
protected var prevX:Number;
protected var prevY:Number;
private var shellA:Array = [];
private var gunL:Number;
private var shellSpeed:int = 10;
private var shotSound:Sound = new ShotSound();
private var destroySound:Sound = new DestroySound();
// Number of foes shot.
private var scoreShots:int = 0;
// I decided to limit the maximum number of shells
// that can exist on-screen at any one time so this
// is not a blast-fest.
private var maxShots:int = 1;
// Number of hits until tank destruction. Needed
// internal because I wanted to use for stats in
// CombatView
internal var maxHits:int = 2
;
// Number of incoming shell hits. Needed internal
// because I wanted to use for stats in CombatView
internal var hits:int;
// array of foes
internal var foeA:Array = [];
// array of friends
internal var friendA:Array = [];
// Shell hit something (so it cannot hit anything
// else and must be removed)
private var shellHit:Boolean;
// A reference to the CombatView instance so Tank can
// exchange some data with CombatView
private var cv:MovieClip;
// Seconds delay until shooting allowed. I was getting
// creamed at the start (especially when there were more
// than a few enemies) so I added a shooting delay. That
Version _08
// lets me get into a better defensive position when the
// firing begins.
private var startTime:int = 3;
// Used for the start shooting delay
private var initTime:int;
// Here’s d2r defined once and used in Tank, PlayerTank
// and EnemyTank
protected const d2r:Number = Math.PI/180;
public function Tank(_xLimit:int,_yLimit:int,_view:MovieClip=null) {
// This called from IntroView where no _view parameter
// is needed and none is passed. If a _view parameter is
// passed it is from CombatView and then it is needed because
// of my ridiculous data handling "scheme". (I actually don’t
// think the data handling exemplified in this code deserves
// to be called a scheme. It is a hodgepodge of devices.)
if(_view){
cv = _view;
}
// Used in CombatView for stats display.
if(cv){
// send maxHits and the start delay to the
// CombatView instance for display purposes
// (the player life remaining and start time
// countdown, resp.).
cv.maxHitsF(maxHits);
cv.startTimeF(startTime);
}
xLimit = _xLimit;
yLimit = _yLimit;
this.addEventListener(Event.ADDED_TO_STAGE,initT);
this.addEventListener(Event.REMOVED_FROM_STAGE, removedF);
}
private function initT(e:Event):void{
// Start a loop to check for tank vs tank collisions
this.addEventListener(Event.ENTER_FRAME,tank_v_tankCollisionsF);
this.removeEventListener(Event.ADDED_TO_STAGE,initT);
// initTime use to ensure no tank starts firing for
// first few seconds
initTime = getTimer()+startTime*1000;
prevX = this.x;
prevY = this.y;
// Initialize number of times tank hit by incoming
hits = 0;
gunL = this.turret_mc.width-this.turret_mc.top_mc.width/2;
161
162
Chapter 6 n Developing a Flash Game
}
private function tank_v_tankCollisionsF(e:Event):void{
// Collisions destroy both tanks. I am assuming all
// tanks (except self) are in foeA or enemyA. At least,
// those (in foeA and friendA) are the only collisions
// checked. I use destroyedBool so I don’t check for
// collisions after one has been detected.
var destroyedBool:Boolean = tank_v_someTanksF(foeA);
// If no foe collision, check for friend collision.
if(!destroyedBool){
// Check for this vs friend collision
tank_v_someTanksF(friendA);
}
}
// ffA = friend/foe Array
private function tank_v_someTanksF(ffA:Array):Boolean{
for(var i:int=0;i<ffA.length;i++){
// Check if there’s a tank base_mc colliding
// with another tank base_mc.
if(this.base_mc.hitTestObject(ffA[i].base_mc)){
// If so, destroy them both. Done
// in destroyF()
destroyF(foeA[i]);
destroyF(this);
// Set this Boolean so the friendA
// isn’t checked
return true;
}
}
return false;
}
protected function shootF(e:MouseEvent):void{
// No shots allowed unless number of shells ok,
// there’s a foe and it’s time to start
if(shellA.length<maxShots && foeA.length>0 &&
getTimer()>initTime){
shotSound.play();
var shell:Shell = new Shell();
shellA.push(shell);
this.parent.addChild(shell);
shell.cos =
Math.cos((this.turret_mc.rotation+this.rotation)*d2r);
shell.sin =
Math.sin((this.turret_mc.rotation+this.rotation)*d2r);
Version _08
// shell’s initial position
shell.tankX = this.x;
shell.tankY = this.y;
// I am going to increment f (by shellSpeed)
// in each loop to determine the distance
// traveled by the shell.
shell.f = gunL;
shell.x = this.x+gunL*shell.cos;
shell.y = this.y+gunL*shell.sin;
if(shellA.length==1){
this.addEventListener(Event.ENTER_FRAME,shellLoopF);
}
}
}
private function shellLoopF(e:Event):void{
for(var i:int=shellA.length-1;i>=0;i– –){
shellHit = false;
// Update the distance the shell has traveled
shellA[i].f+=shellSpeed;
// Assign its x,y using the parameters defined in shootF()
shellA[i].x = shellA[i].tankX+shellA[i].f*shellA[i].cos;
shellA[i].y = shellA[i].tankY+shellA[i].f*shellA[i].sin;
// Check for hit vs foes
hitF(foeA,i);
// If a foe hit and this is player, display stat
if(shellHit && this.name == "player"){
scoreShots++;
cv.score = scoreShots;
} else {
// If this is not player, refresh player
// hits - it’s possible player’s been the
// one hit.
if(this.name!="player"){
// cv.playerHitsF() does not
// increment player hits
// (because it may not have
// been hit), it just triggers
// cv to check player hits and
// update the display
cv.playerHitF();
}
}
if(!shellHit){
// Check for hit vs friends
163
164
Chapter 6 n Developing a Flash Game
hitF(friendA,i);
}
// If shell not removed because of a hit,
// check for boundary violation.
if(!shellHit){
if(boundaryViolationF(shellA[i])){
shellRemoveF(i);
}
}
}
}
// ffA = friend/foe Array
private function hitF(ffA:Array,shellIndex:int):void{
for(var j:int=ffA.length-1;j>=0;j– –){
// Check for shell hit
if(ffA[j].hitTestObject(shellA[shellIndex])){
// If a hit, assign shellHit
shellHit = true;
// Remove the shell
shellRemoveF(shellIndex);
// Update ffA[j]’s hits
ffA[j].hits++;
// Check if ffA[j] has reached
// maxHits limit
if(ffA[j].hits>=maxHits){
// ffA[j] has been destroyed
destroyF(ffA[j]);
}
break;
}
}
}
private function destroyF(tank:Tank):void{
destroySound.play();
// Add explosion.
var explosion:Explosion = new Explosion();
// Listen for last frame to play in explosion
// (where a "removeE" event is dispatched).
explosion.addEventListener("removeE",removeExplosionF);
trace(tank.parent);
tank.parent.addChild(explosion);
// Position explosion where tank was located
explosion.x = tank.x;
explosion.y = tank.y;
Version _08
tank.parent.removeChild(tank);
// Pass tank to cv where it should be removed
// from all arrays.
cv.removeTankF(tank);
if(tank.name=="player"){
// May have been destroyed by tank collision,
// which is no big deal unless tank is player.
// In that case, player will be destroyed but
// the stats will show life remaining. This
// will prevent that issue.
tank.hits = maxHits;
cv.playerHitF()
}
}
private function shellRemoveF(i:int):void{
// ready shellA[i] for gc
this.parent.removeChild(shellA[i]);
shellA.splice(i,1);
if(shellA.length==0){
this.removeEventListener(Event.ENTER_FRAME,shellLoopF);
}
}
protected function moveLimitsF():void{
if(boundaryViolationF(this)){
this.x = prevX;
this.y = prevY;
}
}
// I changed this to be consistent with the notation
// in EnemyTank and decided this is needed because I
// saw a boundary problem while testing other code.
protected function boundaryViolationF(mc:MovieClip):String{
var b:Boolean = false;
if(mc.x<mc.width/2){
b=true;
} else if(mc.x>xLimit-mc.width/2){
b=true;
} else if(mc.y<mc.height/2){
b=true;
} else if(mc.y>yLimit-mc.height/2){
b=true;
}
// If there’s a boundary violation, return the tank’s
165
166
Chapter 6 n Developing a Flash Game
// quadrant, so if this is an EnemyTank, it can turn
// toward stage center after moving backward.
if(b){
return quadrantF();
} else {
return "";
}
}
protected function quadrantF():String{
var quadrant:String;
if(this.x<this.parent.width/2){
quadrant="L";
} else {
quadrant="R";
}
if(this.y<this.parent.height/2){
quadrant+="U";
} else {
quadrant+="D";
}
return quadrant;
}
private function removeExplosionF(e:Event):void{
e.currentTarget.removeEventListener("removeE",removeExplosionF);
// Sometimes an explosion’s parent is null because
// it is not on-stage. When that happens, applying
// removeChild() to it will trigger a null object
// reference error. This if statement prevents that
// null object error
if(e.currentTarget.stage){
e.currentTarget.parent.removeChild(e.currentTarget);
}
}
private function removedF(e:Event):void{
this.removeEventListener(Event.ENTER_FRAME,tank_v_tankCollisionsF);
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
if(shellA.length>0){
this.removeEventListener(Event.ENTER_FRAME,shellLoopF);
for(var i:int=shellA.length-1;i>=0;i– –){
if(shellA[i].parent){
shellA[i].parent.removeChild(shellA[i]);
}
}
}
Version _08
}
}
}
Version _08 PlayerTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Mouse;
public class PlayerTank extends Tank {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var keyCodeIndex:int;
private var directionA:Array = [];
private var cCursor:CustomCursor;
// I added a score variable so player could use this
// to pass data to GameOverView
public var score:int;
public function PlayerTank(_xLimit:int,_yLimit:int,_cv:MovieClip=null) {
// pass the CombatView instance to Tank
super(_xLimit,_yLimit,_cv);
cCursor = new CustomCursor();
this.addEventListener(Event.ADDED_TO_STAGE,initP);
this.addEventListener(Event.REMOVED_FROM_STAGE,removedP);
}
private function initP(e:Event):void{
// no change
}
private function addCustomCursorF(e:MouseEvent):void{
// no change
}
private function removeCustomCursorF(e:MouseEvent):void{
// no change
}
private function rotateTurretF(e:Event):void{
// if statement used to prevent null object error
if(cCursor && this.parent){
cCursor.x = this.parent.mouseX;
cCursor.y = this.parent.mouseY;
167
168
Chapter 6 n Developing a Flash Game
// Using d2r from Tank
this.turret_mc.rotation = -this.rotation+Math.
atan2(this.parent.mouseY-this.y,this.parent.mouseX-this.x)/d2r;
}
}
private function keydownF(e:KeyboardEvent):void{
// no change
}
private function keyupF(e:KeyboardEvent):void{
// no change
}
private function moveTankF(e:Event):void{
// no change
}
private function removedP(e:Event):void{
// Remove the custom cursor if it’s on-stage when
// player removed and show the mouse
if(cCursor.stage){
Mouse.show();
this.parent.removeChild(cCursor);
}
// null the custom cursor so it can be gc’d
// (although, once this instance is gc’d, custom
// cursor should be gc’d)
cCursor = null;
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedP);
this.parent.removeEventListener(MouseEvent.MOUSE_DOWN,
shootF);
this.parent.removeEventListener(MouseEvent.ROLL_OVER,
addCustomCursorF);
this.parent.removeEventListener(MouseEvent.ROLL_OUT,
removeCustomCursorF);
stage.removeEventListener(KeyboardEvent.KEY_DOWN,keydownF);
stage.removeEventListener(KeyboardEvent.KEY_UP,keyupF);
this.removeEventListener(Event.ENTER_FRAME,moveTankF);
this.removeEventListener(Event.ENTER_FRAME,rotateTurretF);
}
}
}
Version _08
Version _08 EnemyTank.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class EnemyTank extends Tank {
private var speed:int = 3;
private var rotationRate:int = 3;
private var xLimit:int;
private var yLimit:int;
private var cv:CombatView;
private var currentDirection:int;
private var nextDirection:int;
private var distance:int;
private var startX:int;
private var startY:int;
private var directionChangeFreq:int = 2500;
private var directionChangeTimer:Timer;
private var moveBackBool:Boolean;
private var moveBackTimer:Timer;
// In case I add an EnemyTank to a non-CombatView, I coded
// a default _cv here. But it’s not currently used.
public function EnemyTank(_xLimit:int,_yLimit:int,_cv:CombatView=null)
{
super(_xLimit,_yLimit,_cv);
colorF();
cv = _cv;
currentDirection = int(360*Math.random());
distance = speed;
this.addEventListener(Event.REMOVED_FROM_STAGE,removedF);
this.addEventListener(Event.ENTER_FRAME,rotateTurretF);
this.addEventListener(Event.ENTER_FRAME,moveTankF);
directionChangeTimer = new Timer(directionChangeFreq,0);
directionChangeTimer.addEventListener(TimerEvent.TIMER,
newDirectionF);
directionChangeTimer.start();
moveBackTimer = new Timer(1000,1);
moveBackTimer.addEventListener(TimerEvent.TIMER,moveBackF);
}
169
170
Chapter 6 n Developing a Flash Game
private function newDirectionF(e:TimerEvent):void{
// ai: turn towards stage center. quadrantF in Tank
var quadrant:String = quadrantF();
// No sense writing the same code twice here and
// in moveLimitsF
turnF(quadrant);
this.addEventListener(Event.ENTER_FRAME,directionChangeF);
}
private function colorF():void{
// no change
}
private function rotateTurretF(e:Event):void{
// Shoot at closest foe. This is for
// free-for-all mode.
var closestFoe:Tank = closestFoeF();
// If there’s a foe, there’s a closest foe,
// but there may not be any foes remaining.
if(closestFoe){
// Update turret rotation, then shoot.
// Use d2r variable
this.turret_mc.rotation = this.rotation+Math.atan2(closestFoe.y-this.y,closestFoe.x-this.x)/d2r;
shootF(null);
}
}
private function closestFoeF():Tank{
// Find the closest foe. Because dist1*dist1<dist2*dist2
// implies dist1<dist2, I can save more cpu cycles by
// using the distance squared instead of the actual
// distance, which would require using Math.sqrt().
// Initialize closestDist to be equal to the stage’s
// diagonal length squared
var closestDist:Number = stage.stageWidth*
stage.stageWidth+stage.stageHeight*stage.stageHeight;
// Initialize closestFoe
var closestFoe:Tank;
// Loop through the foes
for(var i:int=0;i<this.foeA.length;i++){
// Find the distance (squared) from "this" tank
// to foeA[i]
var dist:Number = distF(this.foeA[i]);
// If that is less than closestDist, update
// closestDist and closestFoe.
if(dist<closestDist){
Version _08
closestDist = dist;
closestFoe = this.foeA[i];
}
}
// Return the victim to be shot (at).
return closestFoe;
}
private function distF(t:Tank):Number{
// Math.sqrt() is actual distance, but we just
// need the relative distance.
return (this.x-t.x)*(this.x-t.x)+(this.y-t.y)*(this.y-t.y);
}
private function moveTankF(e:Event):void{
// Another if statement to prevent null object errors
if(this.stage){
if(startX==0){
startX = this.x;
startY = this.y;
}
if(!moveBackBool){
this.rotation = currentDirection;
}
this.x = startX+distance*Math.cos(currentDirection*d2r);
this.y = startY+distance*Math.sin(currentDirection*d2r);
distance += speed;
moveLimitsF();
prevX = this.x;
prevY = this.y;
}
}
override protected function moveLimitsF():void{
var s:String = boundaryViolationF(this);
// If there is a boundary violation, move backwards
if(s){
this.x = prevX;
this.y = prevY;
// Move in opposite direction
directionChangeTimer.stop();
directionChangeUpdateF();
currentDirection = (180+currentDirection)%360;
moveBackBool = true;
moveBackTimer.start();
this.removeEventListener(Event.ENTER_FRAME,directionChangeF);
171
172
Chapter 6 n Developing a Flash Game
// In this version nextDirection is chosen
// more wisely. Rather than turn -90 degrees
// like the previous version, this tank will
// now turn toward stage center.
turnF(s);
}
}
private function turnF(quadrant:String):void{
if(quadrant=="LU"){
nextDirection = int(90*Math.random());
} else if(quadrant=="LD"){
nextDirection = 270+int(90*Math.random());
} else if(quadrant=="RU"){
nextDirection = 90+int(90*Math.random());
} else {
nextDirection = 180+int(90*Math.random());
}
}
private function moveBackF(e:TimerEvent):void{
// no change
}
private function directionChangeF(e:Event):void{
// no change
}
private function directionChangeUpdateF():void{
// no change
}
private function removedF(e:Event):void{
// no change
}
}
}
This works well, so we’re nearing the end of the work on this phase of the tank combat game. Obviously (from my comments), we need to organize the data handling.
While we’re organizing the data so it’s all in our Data class (renamed and expanded
from KBcontrol), we’re adding options for the user to control a number of parameters
from the IntroView class.
Although I’m not a fan of Flash components, I decided to use the slider to speed
finishing this phase of the work. Typically, we would copy the graphics (like we did
for the check box in version _09) and write our own code for handling the object’s
functionality. I thought we would encounter problems that would justify my
Version _09
comment about trying to avoid using components, but I found no problems using
the slider, so I left it in version _09.
There were changes in many of the files. In particular, the FLA has quite a few
changes to the _intro view symbol to allow the control of many more parameters
than just the keyboard control keys, and the old KBcontrol class (now the Data class)
has been expanded to include all those parameters. There were no changes in
PlayerTank or EnemyTank.
Version _09
Version _09 Main.as
public function Main() {
// The first line of code in the constructor is one
// of the two things new in Main.
// Initialize all the new IntroView parameters to
// their default values. Check the static resetAllF()
// function in Data to see how this works.
Data.resetAllF();
addIntroViewF();
}
// The other change was adding a "replayE" event listener and
// its related code to remove the listener and code for the
// listener function.
private function addGameOverViewF(e:Event):void{
removeCombatViewF();
// Add a GameOverView instance
gameOverView = new GameOverView();
// Add a listener for the replay button
gameOverView.addEventListener("replayE",replayF);
addChild(gameOverView);
}
private function removeGameOverViewF():void{
// Ready gameOverView for gc
gameOverView.removeEventListener("replayE",replayF);
removeChild(gameOverView);
gameOverView = null;
}
private function replayF(e:Event):void{
removeGameOverViewF();
// And add an IntroView instance.
addIntroViewF();
}
173
174
Chapter 6 n Developing a Flash Game
Version _09 IntroView.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
import flash.events.Event;
public class IntroView extends MovieClip {
// An array of all MovieClips that contain a slider,
// defined in init(). Used to ease coding for the slider
// mouse listeners and for assigning the initial values
// of the sliders.
private var mc_sliderA:Array;
public function IntroView() {
this.addEventListener(Event.ADDED_TO_STAGE,init);
// Clean up everything in this class when no longer needed.
this.addEventListener(Event.REMOVED_FROM_STAGE,removeF);
}
private function init(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
mc_sliderA =
[tankSpeed_mc,rotationRate_mc,shellSpeed_mc,maxShells_mc,maxHits_mc,enemyNum_mc,
enemyEvasiveness_mc,startTime_mc,combatEndDelay_mc];
listenersF();
addPlayerTankF();
}
private function removeF(e:Event):void{
this.removeEventListener(Event.REMOVED_FROM_STAGE,removeF);
// Remove all the listeners created here
removeListenersF();
// Remove everything including that problematic
// startCombat_mc button that had focus and was
// then removed from the stage.
removeAllF();
}
// add listeners and assign default values
private function listenersF():void{
left_mc.addEventListener(MouseEvent.CLICK,leftF);
left_mc.buttonMode = true;
right_mc.addEventListener(MouseEvent.CLICK,rightF);
right_mc.buttonMode = true;
forward_mc.addEventListener(MouseEvent.CLICK,forwardF);
forward_mc.buttonMode = true;
back_mc.addEventListener(MouseEvent.CLICK,backF);
Version _09
back_mc.buttonMode = true;
startCombat_mc.addEventListener(MouseEvent.CLICK,startCombatF);
startCombat_mc.buttonMode = true;
// Slider listeners
for(var i:int=0;i<mc_sliderA.length;i++){
// I wanted to supply an explanation of what
// each slider controls and I don’t have enough
// room in the _intro view symbol to use static
// text. So, I added a MouseEvent.MOUSE_OVER
// event to cause explanatory text to
// appear in a dynamic TextField.
mc_sliderA[i].addEventListener(MouseEvent.MOUSE_OVER,explanationF);
// I started with a SliderEvent.CHANGE event but
// it updates the value only after releasing the
// slider, not as it is being changed. So, I
// opted for a MouseEvent.MOUSE_MOVE event.
mc_sliderA[i].sl.addEventListener(MouseEvent.MOUSE_MOVE,sliderChangeF);
// Setting the initial values for the sliders.
// The first time this page appears, those are
// the default values (listed in Data). Thereafter,
// the previously set values are used (just like the
// tank key controls).
setValuesF(i);
}
// Coding for the free-for-all MovieClip button.
freeForAll_mc.cbox.addEventListener(MouseEvent.CLICK,freeForAllF);
freeForAll_mc.addEventListener(MouseEvent.MOUSE_OVER,freeForAllExplanationF);
setValuesF(-1);
// Coding for the reset button that resets parameters
// to default values.
reset_mc.addEventListener(MouseEvent.CLICK,resetF);
}
private function removeListenersF():void{
left_mc.removeEventListener(MouseEvent.CLICK,leftF);
right_mc.removeEventListener(MouseEvent.CLICK,rightF);
forward_mc.removeEventListener(MouseEvent.CLICK,forwardF);
back_mc.removeEventListener(MouseEvent.CLICK,backF);
startCombat_mc.removeEventListener(MouseEvent.CLICK,startCombatF);
// The most recently added listeners are listed here to make sure
// I remove all the listeners.
for(var i:int=0;i<mc_sliderA.length;i++){
mc_sliderA[i].removeEventListener(MouseEvent.MOUSE_OVER,explanationF);
175
176
Chapter 6 n Developing a Flash Game
mc_sliderA[i].sl.removeEventListener(MouseEvent.MOUSE_MOVE,sliderChangeF);
}
freeForAll_mc.cbox.removeEventListener(MouseEvent.CLICK,freeForAllF);
freeForAll_mc.removeEventListener(MouseEvent.MOUSE_OVER,
freeForAllExplanationF);
reset_mc.removeEventListener(MouseEvent.CLICK,resetF);
}
// Initial values for the sliders and free-for-all.
private function setValuesF(i:int):void{
if(i>-1){
// Reminder: use the Flash API to find
// Slider class properties (like value,
// minimum and maximum) if you need help
// understanding the slider code.
// You should open Data.as to understand this.
// Data.defaultVariableA is an array of the Data
// variables listed as strings. I could use array
// notation Data[Data.defaultVariableA[i]] to
// access the variables in Data
they were public
// (or internal) variables. But they are not. So, I
// use the getters, which are public. The getter name
// is the same as the variable name without
// the leading underscore. To remove the underscore
// from those strings, I use split("_") and access
// the 2nd array element (index 1).
// For example, to get _tankSpeed in Data, I would
// use Data.tankSpeed. Using array notation that
// would be Data["tankSpeed"] =
Data["_tankSpeed".split("_")[1]] =
// Data[Data.defaultVariableA[1].split("_")[1]]
mc_sliderA[i].sl.value =
Data[Data.defaultVariableA[i+1].split("_")[1]];
mc_sliderA[i].tf.text =
Data[Data.defaultVariableA[i+1].split("_")[1]];
} else {
// freeForAll_mc.cbox has two frames. frame 1
// without the check mark, frame 2 with the
// check mark.
if(Data[Data.defaultVariableA[0].split("_")[1]]){
freeForAll_mc.cbox.gotoAndStop(2);
} else {
freeForAll_mc.cbox.gotoAndStop(1);
}
Version _09
}
}
private function removeAllF():void{
for(var i:int=0;i<this.numChildren;i++){
removeChildAt(0);
}
}
// This is where the text for the IntroView TextField (tf)
// is assigned.
private function explanationF(e:MouseEvent):void{
var val:int = e.currentTarget.sl.value
switch(e.currentTarget.name){
case "tankSpeed_mc":
tf.text = "Controls tank speed from a minimum of
"+e.currentTarget.sl.minimum+" to a maximum of "+e.currentTarget.sl.maximum+"\nCurrent
tank speed is "+val;
break;
case "rotationRate_mc":
tf.text = "Controls tank turret rotation rate from a
minimum of "+e.currentTarget.sl.minimum+" to a maximum of "+e.current
Target.sl.maximum+"\nCurrent turret rotation rate is "+val;
break;
case "shellSpeed_mc":
tf.text = "Controls tank shell speed from a
minimum of "+e.currentTarget.sl.minimum+" to a maximum of
"+e.currentTarget.sl.maximum+"\nCurrent tank shell speed is "+val;
break;
case "maxShells_mc":
tf.text = "Controls maximum number of shells each
tank can have on-screen at any one time. You can adjust from a minimum of
"+e.currentTarget.sl.minimum+" to a maximum of "+e.currentTarget.sl.maximum+"\nCurrent
tank shell maximum number is "+val;
break;
case "maxHits_mc":
tf.text = "Controls number of shell hits each tank
can withstand before destruction. You can adjust from a minimum of
"+e.currentTarget.sl.minimum+" to a maximum of "+e.currentTarget.sl.maximum+"\nCurrent
number of shell hits before destruction is "+val;
break;
case "enemyNum_mc":
tf.text = "Controls the number of enemy tanks.
You can adjust from a minimum of "+e.currentTarget.sl.minimum+" to
a maximum of "+e.currentTarget.sl.maximum+"\nCurrent enemy number is "+val;
break;
177
178
Chapter 6 n Developing a Flash Game
case "enemyEvasiveness_mc":
tf.text = "Controls the frequency of enemy tank
evasive manuevers. You can adjust from a minimum of "+e.currentTarget.sl.minimum+" to
a maximum of "+e.currentTarget.sl.maximum+"\nCurrent enemy tank evasiveness is "+val;
break;
case "startTime_mc":
tf.text = "Controls the delay from the intial combat
screen until the first shot is allowed. You can adjust from a minimum of
"+e.currentTarget.sl.minimum+" second(s) to a maximum of
"+e.currentTarget.sl.maximum+"\nCurrent delay is "+val;
break;
case "combatEndDelay_mc":
tf.text = "Controls the delay from the end of combat
until the End Game Screen. You can adjust from a minimum of
"+e.currentTarget.sl.minimum+" second(s) to a maximum of
"+e.currentTarget.sl.maximum+"\nCurrent delay is "+val;
break;
}
}
// Data class setters called when there is a slider change.
private function sliderChangeF(e:Event):void{
var val:int = int(e.currentTarget.value);
switch(e.currentTarget.parent.name){
case "tankSpeed_mc":
tf.text = "Controls tank speed from a minimum of
"+e.currentTarget.minimum+" to a maximum of "+e.currentTarget.maximum+"\nCurrent tank
speed is "+val;
Data.tankSpeed = val;
e.currentTarget.parent.tf.text = val;
break;
case "rotationRate_mc":
tf.text = "Controls tank turret rotation rate from a
minimum of "+e.currentTarget.minimum+" to a maximum of"+e.currentTarget.maximum+"
\nCurrent turret rotation rate is "+val;
Data.rotationRate = val;
e.currentTarget.parent.tf.text = val;
break;
case "shellSpeed_mc":
tf.text = "Controls tank shell speed from a minimum
of "+e.currentTarget.minimum+" to a maximum of
"+e.currentTarget.maximum+"\nCurrent tank shell speed is "+val;
Data.shellSpeed = val;
e.currentTarget.parent.tf.text = val;
break;
Version _09
case "maxShells_mc":
tf.text = "Controls maximum number of shells each
tank can have on-screen at any one time. You can adjust from a minimum of
"+e.currentTarget.minimum+" to a maximum of "+e.currentTarget.maximum+"\nCurrent tank
shell maximum number is "+val;
Data.maxShells = val;
e.currentTarget.parent.tf.text = val;
break;
case "maxHits_mc":
tf.text = "Controls number of shell hits each tank
can withstand before destruction. You can adjust from a minimum of
"+e.currentTarget.minimum+" to a maximum of "+e.currentTarget.maximum+"\nCurrent
number of shell hits before destruction is "+val;
Data.maxHits = val;
e.currentTarget.parent.tf.text = val;
break;
case "enemyNum_mc":
tf.text = "Controls the number of enemy tanks. You
can adjust from a minimum of "+e.currentTarget.minimum+" to a maximum of
"+e.currentTarget.maximum+"\nCurrent enemy number is "+val;
Data.enemyNum = val;
e.currentTarget.parent.tf.text = val;
break;
case "enemyEvasiveness_mc":
tf.text = "Controls the frequency of enemy tank
evasive manuevers. You can adjust from a minimum of "+e.currentTarget.minimum+" to a
maximum of "+e.currentTarget.maximum+"\nCurrent enemy tank evasiveness is "+val;
Data.enemyEvasiveness = val;
e.currentTarget.parent.tf.text = val;
break;
case "startTime_mc":
tf.text = "Controls the delay from the start of the
combat screen until the first shot is allowed. You can adjust from a minimum of
"+e.currentTarget.minimum+" second(s) to a maximum of
"+e.currentTarget.maximum+"\nCurrent delay is "+val;
Data.startTime = val;
e.currentTarget.parent.tf.text = val;
break;
case "combatEndDelay_mc":
tf.text = "Controls the delay from the end of combat
until the End Game Screen. You can adjust from a minimum of
"+e.currentTarget.minimum+" second(s) to a maximum of
"+e.currentTarget.maximum+"\nCurrent delay is "+val;
Data.combatEndDelay = val;
179
180
Chapter 6 n Developing a Flash Game
e.currentTarget.parent.tf.text = val;
break;
}
}
// freeForAll_mc.cbox clicked:
private function freeForAllF(e:MouseEvent):void{
var mc:MovieClip = MovieClip(e.currentTarget);
// If the check box is displayed frame 1 (unchecked),
// goto frame 2 (checked) and update _freeForAll in
// Data using the setter. Else, goto frame 1 and
// update _freeForAll in Data using the setter.
if(mc.currentFrame==1){
mc.gotoAndStop(2);
Data.freeForAll = true;
} else {
mc.gotoAndStop(1);
Data.freeForAll = false;
}
}
// freeForAll_mc explanation.
private function freeForAllExplanationF(e:MouseEvent):void{
tf.text = "Indicates whether combat is a free-for-all (all tanks
fight each other) or not (it’s you against all enemy tanks)";
}
// Reset all sliders and check box and parameters in Data
private function resetF(e:MouseEvent):void{
Data.resetAllF();
for(var i:int=-1;i<mc_sliderA.length;i++){
setValuesF(i);
}
}
// no change in any of the remaining functions
private function addPlayerTankF():void{
}
private function positionF(parent_mc:MovieClip,mc:MovieClip):void {
}
private function leftF(e:MouseEvent):void{
}
private function rightF(e:MouseEvent):void{
}
private function forwardF(e:MouseEvent):void{
}
private function backF(e:MouseEvent):void{
}
Version _09
private
}
private
}
private
}
private
}
private
}
function leftKeyF(e:KeyboardEvent):void{
function rightKeyF(e:KeyboardEvent):void{
function forwardKeyF(e:KeyboardEvent):void{
function backKeyF(e:KeyboardEvent):void{
function startCombatF(e:MouseEvent):void{
}
}
Version _09 CombatView.as
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class CombatView extends MovieClip {
private var player:PlayerTank;
// Option to control the number of enemy tanks. Get via Data
private var enemyNum:int = Data.enemyNum;
private var enemyA:Array = [];
// maxHits now get via Data
private var maxHits:int = Data.maxHits;
// Time in ms to delay removing this view and adding game
// over view.
// Get from Data
private var timeTilGameOverView:int = 1000*Data.combatEndDelay;
// Get from Data
private var startTime:int = Data.startTime;
public function CombatView() {
// no change
}
private function init(e:Event):void{
// no change
}
private function statsDisplayF():void{
// no change
}
private function addTanksF():void{
var w:int = arena_mc.width;
var h:int = arena_mc.height
181
182
Chapter 6 n Developing a Flash Game
player = new PlayerTank(w,h);
// Add listeners for events dispatched from Tank
// to remove player from the arrays when destroyed,
// register a hit to player and when player "scores"
// or hits another tank
player.addEventListener("removeTankE",removeTankF);
player.addEventListener("playerHitE",playerHitF);
player.addEventListener("scoreE",scoreF);
// Get maxHits from Data
maxHits = Data.maxHits;
player.name = "player";
positionF(arena_mc,player);
// Add enemy tank(s)
for(var i:int=0;i<enemyNum;i++){
var enemy:EnemyTank = new EnemyTank(w,h);
// Add listener for event dispatched from Tank
// when an enemy is destroyed and should be
// removed from arrays.
enemy.addEventListener("removeTankE",removeTankF);
enemy.name = i.toString();
enemy.foeA.push(player);
positionF(arena_mc,enemy);
enemyA.push(enemy);
}
// all enemies are player foes.
player.foeA = player.foeA.concat(enemyA);
// player has no friends. So, I did not define a
// player.friendA. You’re on your own. There is
// now a Data.freeForAll parameter used to indicate
// a free-for-all
if(Data.freeForAll){
for(i=0;i<enemyNum;i++){
// Add all enemies to each enemy’s foeA
enemyA[i].foeA = enemyA[i].foeA.concat(enemyA);
// Remove enemyA[i] so no one is their
// own foe.
enemyA[i].foeA.splice(i+1,1);
}
} else {
for(i=0;i<enemyNum;i++){
// add all enemies to each enemy’s friendA
enemyA[i].friendA = enemyA[i].
friendA.concat(enemyA);
// remove enemyA[i] so no one is their own
Version _09
// friend. At this point, no one targets friends,
// but if a shell hits a friend, it counts. Later
// I may add code to check that no shot is apt to
// hit a friend.
enemyA[i].friendA.splice(i,1);
}
}
// All tanks created. Start countdown to combat:
startTimeF(startTime);
}
// I moved tanks further from the boundary.
private function positionF(arena_mc:MovieClip,mc:MovieClip):void {
arena_mc.addChild(mc);
mc.x = int(2*mc.width+(arena_mc.width-4*mc.width)*Math.
random());
mc.y = int(2*mc.height+(arena_mc.height-4*mc.height)*Math.
random());
// If this is an enemy tank make sure it’s not hitting
// another tank. player is added first so no need to
// check for a hit when it’s added.
if(mc!=player){
if(player.hitTestObject(mc)){
positionF(arena_mc,mc);
} else {
// check mc doesn’t hit any enemyA element
for(var i:int=0;i<enemyA.length;i++){
if(mc.hitTestObject(enemyA[i])){
positionF(arena_mc,mc);
break;
}
}
}
}
}
private function startTimeF(n:int):void{
// Display time until start
startTime_tf.text = n.toString();
// Create timer to count down the start time
// if startTime>0.
if(n>0){
var startTimer:Timer = new Timer(1000,n);
startTimer.addEventListener(TimerEvent.TIMER,timerF);
startTimer.start();
}
183
184
Chapter 6 n Developing a Flash Game
}
// Event dispatched from Tank by destroyed tank uses
// listener function here to call removeTankF. The
// dispatching (= listening) tank is then removed from
// all arrays: enemyA, player.foeA, all enemyA.foeA and
// all enemyA.friendA
public function removeTankF(e:Event):void{
var tank:Tank = Tank(e.currentTarget);
tank.removeEventListener("removeTankE",removeTankF)
// Remove if a player foe.
for(var j2:int=player.foeA.length-1;j2>=0;j2–){
if(player.foeA[j2].name==tank.name){
player.foeA.splice(j2,1);
break;
}
}
// Remove from enemyA before check enemyA element
// foeA and friendA. No need to look for tank in
// its own foeA or enemyA
for(j2=enemyA.length-1;j2>=0;j2–){
if(enemyA[j2].name==tank.name){
enemyA.splice(j2,1);
break;
}
}
// Check if enemyA.length==0. (Two enemy tanks may
// have collided and could end combat.)
if(enemyA.length==0){
playerHitF();
}
for(j2=enemyA.length-1;j2>=0;j2–){
// Remove if an enemy foe
for(var j3:int=enemyA[j2].foeA.length-1;j3>=0;j3–){
if(enemyA[j2].foeA[j3].name==tank.name){
enemyA[j2].foeA.splice(j3,1);
break;
}
}
// Remove if an enemy friend
for(j3=enemyA[j2].friendA.length-1;j3>=0;j3–){
if(enemyA[j2].friendA[j3].name==tank.name){
enemyA[j2].friendA.splice(j3,1);
break;
Version _09
}
}
}
}
// scoreF called via player scoreE event listener when
// player scores a hit on an enemy tank
private function scoreF(e:Event):void{
// Update stats display
score_tf.text = Data.score.toString();
if(enemyA.length == 0){
// If there are no more enemies, the game is over
// and player won. This function is where I delay
// removing this view and adding game over view.
delayLeavingCombatViewF();
}
}
// playerHitF called via player playerHitE event listener
// and possibly from removeTankF()
private function playerHitF(e:Event = null):void{
// This is where maxHits is used to update the
// stats display
life_tf.text = int(100*(maxHits-Data.playerHits)/maxHits)+"%";
if(Data.playerHits==maxHits){
// Game over. You lost.
// Same delay function executes whether player
// wins or loses.
delayLeavingCombatViewF();
}
}
private function timerF(e:TimerEvent):void{
// Update start time countdown
startTime_tf.text = (e.target.repeatCounte.target.currentCount).toString();
if(e.target.currentCount==e.target.repeatCount){
// Remove the listener when no longer needed.
e.target.removeEventListener(TimerEvent.TIMER,timerF);
}
}
private function delayLeavingCombatViewF():void{
// This fixes that problem where player could be
// destroyed, but in free-for-all mode, enemy tanks
// could keep destroying each other, leading to
// possibly erroneous results being displayed in
// the GameOverView instance.
185
186
Chapter 6 n Developing a Flash Game
// Data.enemiesRemaining used in GameOverView
// accurately indicating the number of enemies
// left when player destroyed.
Data.enemiesRemaining = enemyA.length;
var delayTimer:Timer = new Timer(timeTilGameOverView,1);
delayTimer.addEventListener(TimerEvent.TIMER,endCombatF);
delayTimer.start();
}
// Finally, dispatch the endCombatE event so main
// can remove the CombatView instance and add a
// GameOverView instance.
private function endCombatF(e:Event):void{
e.target.removeEventListener(TimerEvent.TIMER,endCombatF);
dispatchEvent(new Event("endCombatE"));
}
private function removedF(e:Event):void{
// Remove this event listener
if(player.hasEventListener("playerHitE")){
player.removeEventListener("playerHitE",playerHitF);
}
// Remove this event listener
if(player.hasEventListener("scoreE")){
player.removeEventListener("scoreE",scoreF);
}
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
for(var i:int=0;i<arena_mc.numChildren;i++){
arena_mc.removeChildAt(0);
}
// empty enemyA
enemyA.length = 0;
player = null;
// There should be no more tank references now
}
}
}
Version _09 GameOverView.as
package com.kglad {
import
import
import
import
import
flash.display.MovieClip;
flash.events.Event;
flash.events.MouseEvent;
flash.text.TextField;
flash.text.TextFormat;
Version _09
import flash.text.Font;
public class GameOverView extends MovieClip {
public function GameOverView() {
// no change
}
private function init(e:Event):void{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
// Using Data.enemiesRemaining, which is defined at
// the end of combat, not when this class is
// instantiated.
if(Data.enemiesRemaining==0){
var s:String = "You won!!\n\nYou had ";
} else {
s = "You lost!\n\nYou had ";
}
s+= Data.score+" enemy hits\nand you were shot "+Data.player
Hits+" times.\n\nThere were "+Data.enemiesRemaining+"
enemies remaining.";
var tfor:TextFormat = new TextFormat();
var verdana:Verdana = new Verdana();
tfor.font = verdana.fontName;
tfor.size = 14;
var tf:TextField = new TextField();
tf.embedFonts = true;
tf.text = s;
tf.setTextFormat(tfor);
tf.multiline = true;
tf.width = 400;
tf.autoSize = "left";
tf.x = (stage.stageWidth-tf.width)/2;
tf.y = 200;
addChild(tf);
}
// replay_mc code
private function listenersF():void{
replay_mc.addEventListener(MouseEvent.CLICK,replayF);
replay_mc.buttonMode = true;
}
private function removedF(e:Event):void{
for(var i:int=0;i<this.numChildren;i++){
this.removeChildAt(0);
}
187
188
Chapter 6 n Developing a Flash Game
this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF);
}
private function replayF(e:Event):void{
replay_mc.removeEventListener(MouseEvent.CLICK,replayF);
dispatchEvent(new Event("replayE"));
}
}
}
Version _09 Tank
package com.kglad {
// The only changes are in the variable declarations:
public class Tank extends MovieClip{
private var xLimit:int;
private var yLimit:int;
protected var prevX:Number;
protected var prevY:Number;
private var shellA:Array = [];
private var gunL:Number;
private var shellSpeed:int = Data.shellSpeed;
private var shotSound:Sound = new ShotSound();
private var destroySound:Sound = new DestroySound();
// Number of foes shot.
private var scoreShots:int = 0;
// Get using Data
private var maxShells:int = Data.maxShells;
// Get using Data
internal var maxHits:int = Data.maxHits;
internal var hits:int;
internal var foeA:Array = [];
internal var friendA:Array = [];
private var shellHit:Boolean;
private var startTime:int = Data.startTime;
// Used for the start shooting delay
private var initTime:int;
protected const d2r:Number = Math.PI/180;
// I am set up to use different speeds and turret
// rotation rates for PlayerTank and EnemyTank, but
// I don’t think I’ll ever use that.
// Two more data getters
protected var speed:int = Data.tankSpeed;
protected var rotationRate:int = Data.rotationRate;
Version _09
Version _09 Data
package com.kglad {
public class Data {
// These are the default arrow-key keyCode’s
private static var _forwardKeyCode:int = 38;
private static var _backKeyCode:int = 40;
private static var _leftKeyCode:int = 37;
private static var _rightKeyCode:int = 39;
// Variables with default values below
private static var _freeForAll:Boolean;
private static var _tankSpeed:int;
private static var _rotationRate:int;
private static var _shellSpeed:int;
private static var _maxShells:int
private static var _enemyNum:int;
private static var _maxHits:int;
private static var _enemyEvasiveness:int;
private static var _startTime:int
private static var _combatEndDelay:int;
// Default values for above variables. Assigned
// in resetAllF() below
private static var defaultValueA:Array = [false,3,3,10,1,5,1,3,3,3];
public static var defaultVariableA:Array =
["_freeForAll","_tankSpeed","_rotationRate","_shellSpeed","_maxShells","_maxHits",
"_enemyNum","_enemyEvasiveness","_startTime","_combatEndDelay"];
// I’m using a static array to store the keycodes. A
// keyCode’s index in _keyCodeA is crucial to determine
// the tank’s response to that keyCode. The first element
// (0-index) is forward, next back, then left, then right.
private static var _keyCodeA:Array =
[_forwardKeyCode,_backKeyCode,_leftKeyCode,_rightKeyCode];
private static var _playerHits:int;
private static var _score:int;
private static var _enemiesRemaining:int;
public function Data() {
// constructor code
}
// keyCodeA is read-only. I.e., there is no setter for it.
public static function get keyCodeA():Array{
return _keyCodeA;
}
// The 4 controls can be set, but there is no need for a
189
190
Chapter 6 n Developing a Flash Game
// getter for them.
public static function set forwardKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[0] = n
}
}
public static function set backKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[1] = n;
}
}
public static function set leftKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[2] = n;
}
}
public static function set rightKeyCode(n:int):void{
if(keyCodeF(n)){
_keyCodeA[3] = n;
}
}
private static function keyCodeF(n:int):Boolean{
if(n>=37&&n<=105){
return true;
} else {
return false;
}
}
public static function set playerHits(n:int):void{
_playerHits = n;
}
public static function get playerHits():int{
return _playerHits;
}
public static function set score(n:int):void{
_score = n;
}
public static function get score():int{
return _score;
}
public static function set enemiesRemaining(n:int):void{
_enemiesRemaining = n;
}
public static function get enemiesRemaining():int{
Version _09
return _enemiesRemaining;
}
public static function set freeForAll(b:Boolean):void{
_freeForAll = b;
}
public static function get freeForAll():Boolean{
return _freeForAll;
}
public static function set tankSpeed(n:int):void{
_tankSpeed = n;
}
public static function get tankSpeed():int{
return _tankSpeed;
}
public static function set rotationRate(n:int):void{
_rotationRate = n;
}
public static function get rotationRate():int{
return _rotationRate;
}
public static function set shellSpeed(n:int):void{
_shellSpeed = n;
}
public static function get shellSpeed():int{
return _shellSpeed;
}
public static function set maxShells(n:int):void{
_maxShells = n;
}
public static function get maxShells():int{
return _maxShells;
}
public static function set maxHits(n:int):void{
_maxHits = n;
}
public static function get maxHits():int{
return _maxHits;
}
public static function set enemyNum(n:int):void{
_enemyNum = n;
}
public static function get enemyNum():int{
return _enemyNum;
}
191
192
Chapter 6 n Developing a Flash Game
public static function set enemyEvasiveness(n:int):void{
_enemyEvasiveness = n;
}
public static function get enemyEvasiveness():int{
return _enemyEvasiveness;
}
public static function set startTime(n:int):void{
_startTime = n;
}
public static function get startTime():int{
return _startTime;
}
public static function set combatEndDelay(n:int):void{
_combatEndDelay = n;
}
public static function get combatEndDelay():int{
return _combatEndDelay;
}
// This is the resetAllF() function used to intialize and
// reset all the non-keyboard parameters
public static function resetAllF():void{
for(var i:int=0;i<defaultValueA.length;i++){
Data[defaultVariableA[i]] = defaultValueA[i];
}
}
}
}
This completes the initial coding of the tank combat game. We could do more, but
this is enough for now.
In the next chapter, I’ll use this game to show you how to test whether a game has a
memory or CPU/GPU utilization problem and how to optimize coding to improve
performance. And then we’ll try to get this game to run on an iPad, where optimization will certainly be needed.
Chapter 7
Optimizing Game
Performance
In this chapter, we’ll explore optimizing Flash game performance. By that I mean
editing your Flash game so its realized (or actual) frame rate is sufficient to make it
appear that your animations are fluid.
In general, you should assign the lowest frame rate that yields smooth animation: A
frame rate of 24 frames per second (fps) will usually suffice to yield fluid animation.
That matches the frame rate used in the motion picture industry.
If you’ve seen stuttering animation, you know what you want to avoid. If you’re not
sure what stuttering animation looks like, create any SWF with animation (or open
the tank combat game), assign a frame rate of less than 10 (for example, 5), and test.
That is stuttering animation.
Two main components determine game performance, and they aren’t independent of
each other: CPU usage and memory usage. I will cover them both in this chapter and
discuss how to optimize each. Some optimizations for one will have a negative
impact on the other. To understand how that works and why you might judiciously
decide to increase memory use to decrease CPU load, for example, read further.
If you make a game for a mobile device, you’ll probably need to use at least some of
the information in this chapter to achieve acceptable frame rates. If your games are
only for the desktop, you can probably achieve acceptable frame rates with some or
no familiarity with the information in this chapter.
193
194
Chapter 7 n Optimizing Game Performance
Judging and Measuring Performance
In an ideal world, part of the Flash test environment would allow you to emulate
your target platform and judge how your game is likely to perform on that target
platform. Unfortunately, unless your development platform and target platform are
the same, currently there is no way to do that.
A useful tool on your development platform would reveal the estimated frame rate of
your game on your target platform. Again, unfortunately no such tool exists yet.
However, you can measure your game’s frame rate using ActionScript, and you can
observe your game’s performance on your test computer. The problem is that your
measurement and observation will reveal the performance of your game when played
on the test platform, not the target platform.
And the two (development platform and target platform) may be vastly different. For
example, when targeting mobile phones, the target platform will certainly be weak
when compared to any platform used for Flash game development (or Flash Pro
wouldn’t even install).
This causes a major workflow problem. For example, it takes so long to publish a
game for a mobile phone, install it, and test it that you will want to do most of
your testing on your development computer. But that doesn’t test your game’s performance on the mobile platform.
Android has a virtual device emulator (http://developer.android.com/guide/
developing/devices/emulator.html), but that doesn’t help with iOS device emulation,
and it doesn’t help reveal the performance that people using different platforms will
see when they play your game. In fact, it doesn’t even come close (at this time) to
accurately demonstrating the performance of your game on an android device, so
it’s not clear to me that the Android emulator is worth using at this time.
Until recently, the best you could do was to measure your app’s performance on your
development platform and periodically confirm that all was well on the target platform by observing your application’s performance on the target platform. That changed at the end of 2012 when Adobe released Adobe Scout (formerly Adobe
Monocle), an application that profiles the performance of Flash content.
You can use Scout to profile your app’s memory use, CPU utilization, frame rate, and
much more. You can even use remote profiling with it, meaning you can run Scout
on your development machine while profiling Flash content running on a mobile
device. There has never been anything like that before, and it is a major benefit for
Flash developers.
CPU/GPU Usage and Memory Consumption
Scout can be downloaded at https://www.adobe.com/products/gaming/tools.edu.html.
Check http://www.adobe.com/devnet/flashruntimes/articles/adobe-scout-getting-started.
edu.html for information on how to use Scout and what you can do with Scout.
There is one thing Scout 1.0 does not yet do, and that is to track which objects are in
memory. Scout is very easy to use to detect a memory leak, but it does not indicate
which objects are causing the leak. So, having an additional tool that can track which
objects are in memory can be very helpful. For that, you can use a memory tracker,
which is discussed below.
It’s a good idea to remember that mobile devices are such weak computing platforms
that you’ll very likely have a performance issue you’ll need to address.
If you learn the most important optimization techniques, you may be able to avoid
the most common causes of performance problems. And if you learn how to test and
compare the various ways to accomplish what you want, you’ll be able to apply the
optimum method to your game and give yourself the best chance for satisfactory
performance.
CPU/GPU Usage and Memory Consumption
How your game performs on a desktop or laptop computer depends on the host
computer’s processing power, which is determined by the host computer’s CPU.
How your game performs on a mobile device not only depends on the device’s
CPU, but may also depend on the device’s graphics processor unit (GPU).
For the rest of this chapter, I’ll use CPU/GPU to mean the processing power of the
host. I intend for that to mean only the CPU when the CPU is the only processor
used by your game and to mean the CPU and GPU when your game uses both.
If the CPU/GPU is overwhelmed by computing something, even if it’s unrelated to
your Flash game, your game will suffer a performance problem. Obviously, you cannot do anything about CPU/GPU usage unrelated to your game, so I’m only going to
discuss CPU/GPU usage caused by your game.
Also, much of what you read here and elsewhere about Flash, especially as it relates
to performance, is dependent on the Flash Player version used or the Adobe Air version used to publish your game. Suffice it to say, there are major differences in performance and memory management when moving from, for example, the iOS player
in Flash Pro CS5 to the player used in Flash Pro CS5.5 to the player used in Flash
Pro CS6. And, there are major differences among the various versions of the standalone Flash Player and major differences among the browser plug-in Flash Players.
195
196
Chapter 7 n Optimizing Game Performance
Everything done by the Flash Player uses CPU/GPU. That means everything displayed in your game and every bit of code that executes in your game requires processing by the CPU/GPU. In addition, if your SWF’s frame rate is greater than zero,
even when there’s nothing on-stage and no code executing, your game still uses CPU/
GPU.
That is, if you open Flash Pro, save your FLA, and publish your SWF, that game still
uses the CPU/GPU when it runs. Even that empty-stage SWF with no code is repeatedly using the CPU/GPU.
For example, if your game’s frame rate is 24, then 24 times per second (if the
host computer/device can cope with that frame rate), the Flash Player does the
following:
1. It checks for any events completed from the previous 1/24 second.
2. Then it executes the listener function code, if there is any, for those events.
3. Then if you have code on a frame that was just entered, that code will execute.
4. Then it dispatches the
event.
Event.ENTER_FRAME
event to all your listeners for that
5. Then the display is rendered.
And, that isn’t all the Flash Player has to do. If you create an object (that is, anything) at any time, the Flash Player has to allocate memory for that object, and it
has to store that object reference and its value, if any was assigned when it was created. If no memory is currently available, Flash allocates more.
But that’s not all the Flash Player has to do. Before it allocates new memory, it checks
for objects that can be removed from memory (i.e., garbage collected). If it finds any,
it marks them for removal and, on a subsequent clock cycle, it clears that part of
memory.
And the best (or worst) is yet to come. The marking of objects for removal is itself a
dizzying multistep process that involves checking every object that exists and then
counting all the references to every existing object. All objects with zero references
are marked for removal (sweeping, in Flash parlance) and (eventually) cleared from
memory.
Other than Steps 3 (game code) and 5 (the graphics displayed), most of us don’t
consider the other steps that impact our game’s performance. However, the time
required to complete all five steps determines whether your game runs smoothly. If
the time required to complete all five steps is greater than 1 fps (frame per second), a
Memory Management
frame will remain on-stage longer than 1 fps. Put a few of those frames together back
to back, and your game will appear to stutter.
I want to convey two main points in this section:
n
There’s a lot going on in even the most basic Flash game, and all of it uses CPU/
GPU.
n
Memory management has an impact not just on memory, but also on CPU/
GPU.
After seeing what the Flash player does many times per second, it is amazing that, for
many gameplayers, there are no noticeable performance problems with most Flash
games. However, an increasing number of players who use relatively weak computing
devices, such as phones, are having problems with Flash games (and everything else
Flash).
For example, my tank combat game plays very smoothly on my development computer. But some computers and probably all mobile devices will experience performance problems with that game unless its performance is improved.
Memory Management
Flash manages memory by allocating it at the start of your game. Then, as mentioned
earlier, if Flash needs more memory, it checks for objects that can be removed and
frees (i.e., deallocates) some previously allocated and previously used memory.
Objects that have no references (in other words, there is no way to address them
using ActionScript) can be removed from memory and are marked by Flash for garbage collection (gc). Actually removing an object from memory occurs at some future
indeterminate (outside of the test environment) time controlled by Flash.
All objects in the display list can be referenced, using getChildAt(), even if you assign
the reference name to null. Therefore, no object in the display list can be gc’d.
All objects with a non-weak listener can be referenced (via its listener function and
the event’s currentTarget property), so all non-weak listeners must be removed before
an object can be gc’d.
At least, that was the way it used to work. In more recent Flash Players, nulled objects
with strong listeners will be gc’d and their listeners removed by Flash. We’ll talk more
about this in the upcoming “Weak Listeners versus Strong Listeners” section.
But, you cannot depend on everyone having a Flash Player version that does that,
and you cannot depend on future Flash Player versions working the same way. So,
197
198
Chapter 7 n Optimizing Game Performance
you should remove all listeners when they’re no longer needed as part of good memory management and CPU/GPU management.
To determine whether you’re practicing good memory management, you need to
measure the memory used by your game. Fortunately, measuring system memory
consumption is easy. ActionScript 3.0 has a static System class property totalMemory
(or totalMemoryNumber if you’re using a lot of system memory) you can use to measure
your game’s memory consumption. Unfortunately, if you have a memory problem,
determining what’s causing it isn’t so easy. I will address that in the next section.
If, at any time after your game starts, the amount of memory required by your game
exceeds the amount of available system memory, your game’s frame rate will drop to
zero, and the Flash Player will crash. Obviously, that is a very serious performance
problem, and it can occur in two general ways.
One way is that upon initial game setup, your game needs more than the available
memory. If that applies to your situation, you need to rethink your game’s setup.
To be sure, there is no sequence of steps that I can sketch that will help in all situations. It may even be that your game is too sophisticated for its target platform, and
nothing short of gutting your game’s essential elements will get it to load and run.
That is, the problem may not even be solvable in any meaningful manner that allows
the game you originally envisioned to load and run.
The second way is that your game’s memory use may increase the longer your game
is played. If your game consumes more and more memory the longer it is played, you
will eventually see your game’s performance degrade. There must be some limit to
memory consumption, or your game will suffer steadily worsening performance and
eventually will crash the Flash Player.
For both excess memory-usage situations, you can test on your development platform. Because the amount of memory your game uses will be the same on the target
platform as on the development platform, you just need to know the amount of
memory available to your game on your target platform and then test your game’s
memory consumption before deploying to that platform.
This second scenario, where your game’s memory use may increase the longer your
game is played, is much more common than the first scenario. It is easy to create
(and re-create ad infinitum) objects that are never cleared from memory. When
that happens, you are certain to see a significant problem eventually.
Fortunately, this runaway memory consumption is always solvable. Although finding
whether a memory problem exists using one of the System properties is
Memory Tracking, Memory Use, and Performance Testing
straightforward, actually pinpointing the problem is not straightforward. Nothing in
Flash Pro lets you track what is in the memory used by your Flash game.
Among the ways you can test a game’s performance and track memory problems is
to use a memory tracker class, which tracks what is in the memory used by your
Flash game. One such class follows. It monitors your game’s realized frames per second and its memory use, and it lets you track what is in your game’s memory.
You should always test your game before deployment, even if there appears to be no
performance problem obvious to you. You should always test to ensure that your
game has no memory problem before testing performance on the target platform.
This is especially important for games deployed for mobile devices. While browserbased games are unlikely to remain open for weeks, it is typical for mobile users to
never close or shut down a game. They are moved to the background and idle when
not in use. Mobile games (and apps) are rarely (if ever) closed, so memory problems
can accumulate over not just weeks, but months of gameplay.
Memory Tracking, Memory Use, and Performance
Testing
The following code is adapted from Damian Connolly’s code at divillysausages.com.
The MT class reports frame rate, memory consumption, and what objects are still in
memory. Using it is easy: Import it and initialize it from the document class:
import com.kglad.MT;
MT.init(this,reportFrequency);
where
this references your
reportFrequency is an integer.
document class (that is, the main timeline) and
The main timeline reference is used to compute the
realized frame rate, and reportFrequency is the frequency in seconds that you want
trace output reporting the frame rate and amount of memory consumed by your
Flash game. If you don’t want any frame rate/memory reporting, pass 0 (or anything
less). You can still use the memory tracker part of this class.
To track objects you create in your game, use:
MT.track(whatever_object,any_detail);
where the first parameter is the object you want to track (to see whether it is ever removed
from memory) and the second parameter is anything you want (typically a string that
supplies details about what, where, and/or when you started tracking that object).
When you want a report of whether your tracked objects still exist in memory, use:
MT.report();
199
200
Chapter 7 n Optimizing Game Performance
I’ll show sample code using MT after discussing details of the MT class in the MT class
comments. You don’t need to understand the class to use it, but it’s a good idea to
check how the Dictionary class is used to store weak references to all the objects
passed to MT.track().
Similar to the observer effect in physics, the mere fact that we are measuring the
frame rate and/or memory and/or tracking memory changes the frame rate and
memory of the game. However, the effect of measurement should be minimal if the
trace output is relatively infrequent. In addition, the absolute numbers usually aren’t
important. It is the change in frame rate and/or memory use over time that is important, and for that this class works well.
This class doesn’t allow more than once per second trace output to help minimize
spuriously low frame rate reports caused by frequent use of trace (in other words,
frequent trace output slows performance). And, you can always eliminate trace output as a confounder of frame rate determination by using a textfield instead of trace
output.
The MT class is the only tool you need to check memory usage and pinpoint memory
problems, as well as indirectly measure CPU/GPU usage (by checking the actual
frame rate of an executing app).
com.kglad.MT
package com.kglad{
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.getTimer;
import flash.system.System;
import flash.utils.Dictionary;
// adapted from @author Damian Connolly
// http://divillysausages.com/blog/tracking_memory_leaks_in_as3
public class MT {
// Used to calculate the real frame rate
private static var startTime:int=getTimer();
// Used to generate trace output intermittently
private static var traceN:int=0;
// Megabyte constant to convert from bytes to megabytes.
private static const MB:int=1024*1024;
private static var mc:MovieClip;
// d is used to store weak references to objects that you
// want to track. It is the essential object used to memory
// track in this class. The true parameter used in the
// constructor designates that all references are weak.
Memory Tracking, Memory Use, and Performance Testing
// That is, the dictionary reference itself won’t prohibit
// the object from being gc’d.
private static var d:Dictionary = new Dictionary(true);
// Used to trigger a report on the tracked objects
private static var reportBool:Boolean;
// Used to (help) ensure gc takes place
private static var gcN:int;
// This is the variable that will store the reportFrequency
// value that you pass.
private static var freq:int;
// A constructor is not needed
public function MT() {
}
// traceF() is the listener function for an Enter.ENTER_FRAME
// event. If the game is running at its maximum frame rate
// (=stage.frameRate), traceF() will be called stage.frameRate
// times per second.
private static function traceF(e:Event):void {
traceN++;
// This conditional ensures trace output occurs no more
// frequently than every freq seconds. If the game is
// running at stage.frameRate, output will occur at
// approximately freq seconds.
if (traceN%(freq*mc.stage.frameRate)==0) {
// This is used to (try and) force gc.
gcN = 0;
forceGC();
trace("FPS:",int(traceN*1000/(getTimer()startTime)),"||","Memory Use:",int(100*System.totalMemory/MB)/100," MB");
traceN=0;
startTime=getTimer();
}
}
// Called just prior to the above trace() and called just
// prior to a memory report. When called just prior to a
// memory report, reportBool is assigned true.
private static function forceGC():void {
// The first call to System.gc() marks items that are
// available for gc. The second should sweep them and
// the third is for good luck because you can’t count
// on anything being predictably gc’d when you think it
// should be.
mc.addEventListener(Event.ENTER_FRAME,gcF,false,0,true);
gcN = 0;
201
202
Chapter 7 n Optimizing Game Performance
}
private static function gcF(e:Event):void {
// System.gc() initiates the gc process during testing
// only. It does not work outside the test environment.
// So, if you are using this class to test a game that
// is installed on a mobile device, System.gc() will not
// clear memory of objects ready to be gc’d.
System.gc();
gcN++;
// 3 System.gc() statements is usually enough to clear
// memory of objects that can be cleared from memory.
if (gcN>2) {
mc.removeEventListener(Event.ENTER_FRAME,gcF,false);
// Here’s where reportBool being true triggers
// the memory report.
if(reportBool){
reportBool = false;
reportF();
}
}
}
// Memory report. All objects passed to d in the track()
// function, if they still exist, will be displayed by
// the trace statement along with the additional information
// you passed to track()
private static function reportF():void{
trace("** MEMORY REPORT AT:",int(getTimer()/1000));
for(var obj:* in d){
trace(obj,"exists",d[obj]);
}
}
public static function init(_mc:MovieClip,_freq:int=0):void{
mc=_mc;
freq=_freq;
if(freq>0){
mc.addEventListener(Event.ENTER_FRAME,traceF,false,0,
true);
}
}
// This the function you use to pass objects you want tracked.
public static function track(obj:*,detail:*=null):void{
d[obj] = detail;
}
Optimization Techniques
// This is the function that triggers a memory report.
public static function report():void{
reportBool = true;
forceGC();
}
}
}
Before I even applied this to the tank combat game, I took some steps to help ensure
that I wouldn’t have runaway memory consumption. In the previous chapter, I
started using an Event.REMOVED_FROM_STAGE listener for all my DisplayObject classes
and doing cleanup in the listener function. That cleanup consisted of removing the
listeners I had previously added and making sure objects created in the class were
removed from the display list and nulled.
If you do that so there are no references to an object, it is not in the display, and it
has no (strong) listeners, then it should be eligible for garbage collection (gc). Unfortunately, there are exceptions to that rule.
While testing the tank combat game, I found an unexpected issue with sounds and
an unexpected issue with timers. There are probably more exceptions.
Optimization Techniques
Unfortunately, I know of no completely satisfactory way to organize the information
on optimization techniques. In what follows, I’ll discuss memory management, with
subtopics listed in alphabetical order. Then I’ll discuss CPU/GPU management, with
subtopics listed in alphabetical order.
That may seem logical, but there are at least two problems with that organization.
n
I don’t believe it’s the most helpful way to organize this information.
n
Memory management affects CPU/GPU usage, so everything in the Memory
Management section could also be listed in the CPU/GPU section.
Anyway, first I’m going to also list the information in two other ways—from easiest
to hardest to implement, and from greatest to least benefit. Both of these listings are
subjective and depend on developer experience and capabilities, as well as the test
situation and test environment. I very much doubt that there could be a consensus
on ordering of these lists. Nevertheless, I still think they’re worthwhile.
203
204
Chapter 7 n Optimizing Game Performance
Easiest to Hardest to Implement
1. Do not use
Filters.
2. Always use reverse
3. Explicitly stop
for
Timers
loops and avoid
do
loops and
while
loops.
to ready them for gc.
4. Use weak event listeners and remove listeners.
5. Strictly type variables whenever possible.
6. Explicitly disable mouse interactivity when it isn’t needed.
7. Replace
8. Stop
dispatchEvents
Sounds
to enable
9. Use the most basic
10. Always use
devices).
11. Reuse
12.
with callback functions whenever possible.
Sounds
DisplayObject
cacheAsBitmap
Objects
and
and
SoundChannels
to be gc’d.
needed.
cacheAsBitmapMatrix
with air apps (that is, mobile
whenever possible.
loops: Use different listeners and different listener functions
applied to as few DisplayObjects as possible.
Event.ENTER_FRAME
13. Pool
Objects
instead of creating and gc’ing
Objects.
14. Use partial blitting.
15. Use stage blitting.
16. Use
Stage3D.
Greatest to Least Benefit
1. Use stage blitting (if there is enough system memory).
2. Use
Stage3D.
3. Use partial blitting.
4. Use
cacheAsBitmap
and
cacheAsBitmapMatrix
with mobile devices.
5. Explicitly disable mouse interactivity when it isn’t needed.
6. Do not use
Filters.
7. Use the most basic
DisplayObject
needed.
Optimization Techniques
8. Reuse
9.
Objects
whenever possible.
loops: Use different listeners and different listener functions
applied to as few DisplayObjects as possible.
Event.ENTER_FRAME
10. Use reverse
11. Pool
for
Objects
loops and avoid
do
loops and
instead of creating and gc’ing
while
loops.
Objects.
12. Strictly type variables whenever possible.
13. Use weak event listeners and remove listeners.
14. Replace
dispatchEvents
15. Explicitly stop
16. Stop
Sounds
Timers
to enable
with callback functions whenever possible.
to ready them for gc.
Sounds
and
SoundChannels
to be gc’d.
Memory Management
Events, Filters, DisplayObjects, Object pooling, Sounds, Timers and Listeners.
Callback Function versus dispatchEvent
There is an increase in memory use when dispatching events because the event must
be created, memory must be allocated, and so on. That makes sense because events
are objects and therefore require memory.
A simple test shows that different event types use different amounts of memory.
var e:Event = new Event("testE");
var me:MouseEvent = new MouseEvent("testE");
var te:TouchEvent = new TouchEvent("testE")
var ae:AccelerometerEvent = new AccelerometerEvent("testE")
var de:DataEvent = new DataEvent("testE");
var ge:GestureEvent = new GestureEvent("testE");
var ee:ErrorEvent = new ErrorEvent("testE");
var fe:FocusEvent = new FocusEvent("testE");
var fse:FullScreenEvent = new FullScreenEvent("testE");
var tx:TextEvent = new TextEvent("testE");
// getSize returns the argument’s size in bytes
trace(getSize(e),getSize(me),getSize(te),getSize(ae),getSize(de),getSize(ge),
getSize(ee),getSize(fe),getSize(fse),getSize(tx))
// output in bytes: 40 104 128 72 48 72 52 56 48 48
205
206
Chapter 7 n Optimizing Game Performance
Using callback functions used less memory and ran more efficiently than using
events. (See the test files in /support files/Chapter 07/callback_v_dispatchEvent.)
Filters
Using a dynamic filter increases memory use. According to Adobe documents (http://
help.adobe.com/en_US/as3/mobile/flashplatform_optimizing_content.pdf), using a
filter doubles memory use. However, while I see an increase in memory use when
using filters, I don’t see anything close to doubling of memory use when testing
with Flash Pro CS6. When testing, I saw a 2-MB increase in memory use (from
12 MB to 14 MB) for 2,000 objects with filters, and a 0.2-MB increase in memory
use (from 4.6 MB to 4.8 MB) for 200 objects. (See the test files in /support files/
Chapter 07/filters.)
Objects
DisplayObjects, Object pooling, and Object reuse.
Display Objects The Shape, Sprite, and MovieClip objects have different memory
requirements. A
bytes.
Shape
requires 236 bytes, a Sprite 412 bytes, and a
MovieClip
448
If you’re using many thousands of display objects, you may be able to save substantial memory by using a Shape if no interactivity is needed and a Sprite if no timeline
is needed. But if you had thousands of display objects, you would need to use optimization beyond using a different display object type. Under normal circumstances
there’s not much to be gained by changing from one display type to another.
For example, in the tank combat game, I could’ve had the Tank class extend the Shape
instead of the MovieClip class. That would save a maximum of 21 tanks x 212 bytes
per tank/1024 bytes per KB, which is less than 4.4 KB. That’s not enough to make me
seriously consider changing to use the Shape object type in the tank combat game.
Object Pooling The concept of object pooling is simple. At the start of your game,
you create all the objects you’ll ever need during the entire time your game is played
and pool them in an array. Whenever an object is needed, you retrieve it from the
array. Whenever an object is no longer needed, you return it to the array.
This releases the Flash Player from gc work. It no longer has to mark and sweep and
allocate new memory. The only downsides are that your game will use the maximum
Optimization Techniques
amount of memory at the game’s start, and there is a little additional coding. Neither
of these is much of a drawback.
Because the main point of using object pooling is efficiency, you’ll typically use a vector instead of an array. Using a vector may be twice as fast as using an array, but
unless you’re doing many hundreds of thousands of operations, you won’t likely
notice a difference because both are fast when limited to thousands of operations.
(See the array_v_vector folder.)
Here’s code for a com.kglad.IntroView_Pool class.
package com.kglad{
public class IntroView_Pool {
private static var pool:Vector.<IntroView>;
public static function init(poolSize:int):void {
pool = new Vector.<IntroView>(poolSize);
for(var i:int=0;i<pool.length;i++){
pool[i] = new IntroView();
}
}
public static function retrieveF():IntroView {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new IntroView();
}
}
public static function returnF(view:IntroView):void {
pool.push(view);
}
}
}
The IntroView_Pool class will use a poolSize of 1 because that is the most IntroView
instances we would need at any one time. But this still will be helpful and will save
the repeated creation and gc’ing of IntroView instances every time a game is replayed.
Object pooling should be even more helpful for EnemyTank instances where we could
create the maximum number (20) in an EnemyTank_Pool class and avoid repeated creation and gc’ing of 1 to 20 EnemyTank instances with each game replay.
When using object pooling, make sure there’s not much more in the object constructor than an Event.ADDED_TO_STAGE event. In particular, you don’t want any
Event.ENTER_FRAME or Timer listeners added to objects you were going to pool. And,
207
208
Chapter 7 n Optimizing Game Performance
you don’t want to null those objects and ready them for gc when you’re finished with
them. You should return them to the pool using something like returnF() in
IntroView_Pool.
Here’s sample code to use
IntroView_Pool.
To initialize the pool, use:
IntroView_Pool.init(1);
in the
Main
constructor. Then in
addIntroViewF(),
instead of:
introView = new IntroView();
you would use:
introView = IntroView_Pool.retrieveF();
and instead of:
introView = null;
in
removeIntroViewF(),
you would use:
IntroView_Pool.returnF(introView);
You still want to remove the
introView
listener in
removeIntroViewF().
Reuse Objects Whenever you create objects in a loop, see whether you can create
one object outside the loop and reuse it repeatedly inside the loop. You can’t always
do that, but there are many places where you can.
For example, in the tank combat game, I was able to use one shellShot sound and
one destroyTank sound repeatedly. And later, in the “Blitting” section, I’ll reuse a
number of objects.
Sounds
The issue with sounds is relatively minor. When a sound is playing, it cannot be gc’d
(at least not when using Flash Pro CS6 to test). When the sound completes playing
or a SoundChannel instance is used to stop() the sound, the sound can be gc’d. (See
below or the test files in /support files/Chapter 07/gc_sound_test.)
import flash.events.TimerEvent;
import flash.media.SoundChannel;
import com.kglad.MT;
MT.init(this,0);
// Sound test showing sound must not be playing in order to be gc’d.
// If soundchannel used, must explictly apply stop() to it for sound
// and soundchannel to be eligible for gc.
Optimization Techniques
// If no soundchannel used, sound is only eligible for gc after sound
// completes play.
var sc:SoundChannel;
for(var i:int=0;i<6;i++){
// ShotSound is 7.3 seconds in duration.
var s:Sound = new ShotSound();
// Comment out one of the two following lines to see the
// difference between using a SoundChannel instance to stop
// play and not using one
//s.play();
sc = s.play();
MT.track(s,"sound_"+i);
if(sc){
MT.track(sc,"soundchannel_"+i);
}
}
// If gc mark/sweep after sound’s duration, sounds and soundchannels
// eligible for gc. Otherwise, if gc mark/sweep before sound’s duration,
// the sound is only eligible for gc if a soundchannel is used AND if stop()
// is applied to the soundchannel. In that situation, not only the sound
// but the soundchannel is eligible for gc.
// Comment out one of the two lines below to see the difference
// between how Flash handles sounds that have and have not completed play.
var delay:int = 7000;
//var delay:int = 8000;
var t:Timer = new Timer(delay,1);
t.addEventListener(TimerEvent.TIMER,stopF);
t.start();
function stopF(e:TimerEvent):void{
if(sc){
sc.stop();
sc=null;
}
if(s){
s=null;
}
MT.report();
}
This isn’t too troubling. It explains why we were sometimes seeing more than two
Sound instances when we were testing the tank combat game, but otherwise it’s not
significant as long as sounds end shortly after they are no longer needed. Sounds otherwise readied for gc will eventually be gc’d after they complete playing if the Flash
Player needs memory.
209
210
Chapter 7 n Optimizing Game Performance
That shouldn’t cause problems unless you have a lot of dead space at the end of your
sounds. Then you should probably use a SoundChannel to stop your sound when it’s
no longer needed or, even better, use a sound editor to remove that dead space.
Timers
The issue with Timers is more serious. If a Timer hasn’t stopped (because its
currentCount is less than its repeatCount and because stop() hasn’t been applied to
it), it won’t be gc’d even if you remove its listener and null all references. Its listener
function won’t be called once you remove the listener, but the object itself will still
consume memory.
In the tank combat game, we had a directionChange Timer that had no end
(repeatCount=0). It repeatedly called a function that changed the EnemyTank’s direction. Actually, each EnemyTank had its own Timer, so when several enemies were
used, several of these Timers were created. And, they were never gc’d because we weren’t aware they needed to be stopped. We wouldn’t have found that issue if we hadn’t
used MT and seen that numerous timers were being created and never gc’d round after
round of play.
A timer uses 72 bytes of memory, so you wouldn’t expect a noticeable problem in a
desktop/browser Flash game. Even after 100 rounds with 20 Timers being created and
not gc’d, you would only have 72 × 100 × 20/1024 < 141 KB additional memory
consumption. But, on a mobile device, for example, a game could be opened and
played, idled, opened and played, and so on without ever restarting, and that could
cause a noticeable problem.
Here is the code I used to confirm the issue. (See the test files in /support files/
Chapter 07/gc_timer_test.)
import flash.events.TimerEvent;
import com.kglad.MT;
MT.init(this,0);
// Timer test showing timer.stop() needed unless timer
// currentCount == repeatCount
// If timer.stop() used, the listener doesn’t even need to be removed
// for the timer to be gc’d.
var t:Timer = new Timer(100,0);
// Even with a weak listener and even when that listener is removed,
// it won’t be gc’d unless it’s explicitly stopped.
t.addEventListener(TimerEvent.TIMER,traceF,false,0,true);
t.start();
MT.track(t,"timer");
Optimization Techniques
function traceF(e:TimerEvent):void{
//t.stop(); // <– needed. Uncomment this and the timer is removed
// even if you comment out the line below that removes the listener.
t.removeEventListener(TimerEvent.TIMER,traceF,false);
t=null;
// The code below is used to check if the timer has been removed
// even at a later time.
var t1:Timer = new Timer(1000,10);
t1.addEventListener(TimerEvent.TIMER,reportF);
t1.start();
}
function reportF(e:TimerEvent):void{
MT.report();
}
Weak Listeners versus Strong Listeners
Another unexpected result of testing with MT is that it makes no difference whether
you use weak or strong listeners. They are both treated like weak listeners and do not
prevent gc’ing of the event dispatcher. (See below or the test files in /support files/
Chapter 07/strong_v_weak_listeners.)
import flash.display.MovieClip;
import flash.events.Event;
import com.kglad.MT;
MT.init(this,0);
for(var i:int=0;i<12;i++){
var mc:MovieClip = new MovieClip();
mc.name = i.toString();
// even with strong listeners, these movieclips are all gc’d
// there are no references to the first 11 by the end of the loop
// and the refrence to the 12th mc is nulled below.
mc.addEventListener(Event.ENTER_FRAME,ff);
// If you uncomment addChild(mc), none of the mc will be gc’d.
//addChild(mc);
MT.track(mc,"exists "+i);
}
function ff(e:Event):void{
trace(e.currentTarget.name);
}
// If you comment-out the next line, the last created mc will continue
// to exist and its listener will continue to call ff().
mc=null;
MT.report();
211
212
Chapter 7 n Optimizing Game Performance
Managing CPU/GPU Usage
The only way I know to currently measure CPU/GPU usage directly is to use an
operating system tool. Windows has the Windows Task Manager (Performance
tab), and Mac has the Activity Monitor. Both allow you to see CPU usage, but neither is very useful for testing Flash game performance.
So, you are left measuring this indirectly by checking your game’s actual frame rate.
Among other things, the MT class does that.
Arithmetic Operations
You can gain efficiency by using bitwise operators and shortcutting some of the Math
class methods. But unless you’re performing many millions of operations, none of the
bitwise methods are worth the trouble, in my opinion.
Bitwise Operators
You can easily apply bitwise operators to any situation where you multiply or divide
by a power of two. For example, instead of halving (frequently used for centering
objects with a corner registration point), you can use a bitwise shift:
x/2 = x>>1
This makes almost no difference at all but is so widely repeated as a benefit that it
seems to be an accepted fact. Maybe it was (and is) a greater benefit in some Flash
Player versions, but although I’ve seen bitwise shifting touted on numerous websites,
none has cited any test data to support the claim.
Here are the results of my testing in /support files/Chapter 07/bitwise_arithmetic/
halving.fla.
Division
/*
No significant difference using bitwise operation vs division. This results below were
the most favorable to bitwise division. Some tests showed faster execution using
multiplication and/or division.
*/
var xx:Number;
var n:int = 10000000;
var i:int;
var startTime:int;
///*
startTime = getTimer()
Managing CPU/GPU Usage
// division duration 445
for(i=0;i<n;i++){
xx = i/2;
}
trace("division duration",getTimer()-startTime);
//*/
///*
startTime = getTimer()
// multiplication duration 440
for(i=0;i<n;i++){
xx = i*.5;
}
trace("multiplication duration",getTimer()-startTime);
//*/
///*
startTime = getTimer()
// bitwise duration 435
for(i=0;i<n;i++){
xx = i>>1;
}
trace("bitwise duration",getTimer()-startTime);
//*/
It makes even less sense to use bitwise arithmetic for doubling. (See/support files/
Chapter 07/bitwise_arithmetic/halving.fla.)
Multiplication
// No significant benefit using bitwise operation vs multiplication
var xx:int;
var n:int = 10000000;
var i:int;
var startTime:int;
///*
startTime = getTimer();
// arithmetic duration 434
for(i=0;i<n;i++){
xx = i*2;
}
trace("arithmetic duration",getTimer()-startTime);
//*/
///*
startTime = getTimer();
// bitwise duration 431
for(i=0;i<n;i++){
213
214
Chapter 7 n Optimizing Game Performance
xx = i<<1;
}
trace("bitwise duration",getTimer()-startTime);
//*/
Likewise, using bitwise shifting instead of multiplying and dividing by other powers
of two provides negligible benefit.
There is a small benefit to using bitwise arithmetic in place of the modulo operator.
(See /support files/Chapter 07/bitwise_arithmetic/modulo.fla.)
Modulo
// small benefit using bitwise operation vs modulo
var p:int = 37;
var i:int;
var n:int = 10000000;
var startTime:int;
/*
startTime = getTimer()
// modulo duration 565
for(i=38;i<n;i++){
if(i%p==0){
}
}
trace("modulo duration",getTimer()-startTime);
*/
/*
startTime = getTimer()
// bitwise duration 451
var p_1:int = p-1;
for(i=38;i<n;i++){
if((i&(p_1))==0){
}
}
trace("bitwise duration",getTimer()-startTime);
*/
///*
// bitwise duration 452
for(i=38;i<n;i++){
if((i&(p-1))==0){
}
}
trace("bitwise duration",getTimer()-startTime);
//*/
Managing CPU/GPU Usage
Math Class Shortcuts These methods actually do provide a benefit, and that benefit
can be significant even without needing millions of operations to realize. They are
listed in alphabetical order starting with Math.abs. (See /support files/Chapter 07/
math_class/Math.abs_v_conditional.fla, /support files/Chapter 07/math_class/Math
.floor_v_int.fla, and so on.)
Math.abs
Using:
x = (y<0) ? -y: y;
instead of:
x = Math.abs(y)
is about twice as fast. Unless you’re using millions of Math.abs operations, you
shouldn’t expect a noticeable benefit from using the inline code. In addition, the
inline code is cumbersome.
var xx:int;
var n:int = 10000000;
var n2:int = n/2;
var i:int;
var startTime:int = getTimer();
//// Math.abs duration: 1016
for(i=0;i<n;i++){
xx = Math.abs(n2-i);
}
//*/
trace("Math.abs duration:",getTimer()-startTime);
///*
// conditional duration: 445
startTime = getTimer();
for(i=0;i<n;i++){
xx = (n2-i<0) ? i-n2 : n2-i;
}
//*/
trace("conditional duration:",getTimer()-startTime);
Math.ceil and Math.floor
Using:
x = int(y);
instead of:
215
216
Chapter 7 n Optimizing Game Performance
x = Math.floor(y); // y>=0
x = Math.ceil(y); // y<=0
is about twice as fast. Unless you’re using millions of Math.floor operations (with
nonnegative numbers), you shouldn’t expect a noticeable benefit.
var i:int;
var n:int = 10000000;
var xx:int;
var startTime:int = getTimer();
///*
// Math.floor duration: 1105
for(i=0;i<n;i++){
xx = Math.floor(i/n);
}
trace("Math.floor duration:",getTimer()-startTime);
//*/
///*
// int duration: 479
startTime = getTimer();
for(i=0;i<n;i++){
xx = int(i/n);
}
//*/
trace("int duration:",getTimer()-startTime);
Math.max
Using:
x = (i>j) ? i : j;
instead of:
x = Math.max(i,j);
is about twice as fast.
This shortcut is also cumbersome but has the greatest benefit (along with Math.min)
of all those listed in this section. Notice the difference in time required to execute the
code blocks and how few iterations are needed to demonstrate that difference.
var xx:int;
var n:int = 1000;
var i:int;
var j:int;
var startTime:int;
///*
Managing CPU/GPU Usage
// Math.max duration: 109
startTime = getTimer();
for(i=n-1;i>=0;i– –){
for(j=n-1;j>-0;j– –){
xx = Math.max(i,j);
}
}
trace("Math.max duration:",getTimer()-startTime);
//*/
///*
// conditional duration 43
startTime = getTimer();
for(i=n-1;i>=0;i– –){
for(j=n-1;j>-0;j– –){
xx = (i>j) ? i : j;
}
}
//*/
trace("conditional duration",getTimer()-startTime);
Math.min
Using:
x = (i<j) ? i : j;
instead of:
x = Math.min(i,j);
is about twice as fast.
This shortcut is also cumbersome but has the greatest benefit (along with Math.max)
of all those listed in this section. Notice the difference in time required to execute the
code blocks and how few iterations are needed to demonstrate that difference.
var xx:int;
var n:int = 1000;
var i:int;
var j:int;
var startTime:int;
///*
// Duration Math.min 121
startTime = getTimer();
for(i=0;i<n;i++){
for(j=0;j<n;j++){
xx = Math.min(i,j);
}
217
218
Chapter 7 n Optimizing Game Performance
}
//*/
trace("Duration Math.min",getTimer()-startTime);
///*
// Duration conditional 43
startTime = getTimer();
for(i=0;i<n;i++){
for(j=0;j<n;j++){
xx = (i<j) ? i : j;
}
}
//*/
trace("Duration conditional",getTimer()-startTime);
Math.pow
It is two to three times faster to explicitly multiply a number variable than it is to use
Math.pow. That benefit even extends a little beyond integer exponents because Math.
sqrt(i) is about twice as fast as Math.pow(i,.5).
var i:int;
var n:int = 10000000;
var xx:int;
var startTime:int;
///*
// exp .5: 2020, exp 2: 1533, exp 3: 1617, exp 4: 1427, exp 5: 1381,
// exp 10: 1391
startTime = getTimer();
for(i=0;i<n;i++){
xx = Math.pow(i,.5);
}
trace("Duration Math.pow",getTimer()-startTime);
//*/
///*
// exp .5: 1064, exp 2: 427, exp 3: 778, exp 4: 557, exp 5: 501,
// exp 10: 586
startTime = getTimer();
for(i=0;i<n;i++){
xx = Math.sqrt(i);
}
trace("Duration iteration",getTimer()-startTime);
//*/
Bitmaps
cacheAsBitmap, cacheAsBitmapMatrix, blitting and partial blitting.
Managing CPU/GPU Usage
cacheAsBitmap and cacheAsBitmapMatrix
One of the most basic methods to decrease CPU usage is to minimize the work of
the Flash Player and CPU when the display is rendered by using bitmaps or
DisplayObjects that have their cacheAsBitmap property enabled. You should see a
major performance boost when applying cacheAsBitmap appropriately. You should
also see memory utilization increase because a bitmap of that vector DisplayObject
is created in memory and used to render the display.
Enabling the cacheAsBitmap property will significantly improve performance as long as
the DisplayObject doesn’t undergo any changes that require an update to the bitmap.
Essentially, that means your DisplayObject doesn’t change appearance in any way
other than changing its location on the stage. That is, its x and/or y can change, but
anything else will require an update to the bitmap. If there are frequent bitmap
updates, performance will decrease rather than improve.
How frequently you can update a cached bitmap and still see a performance benefit
depends on several factors, the most important of which is, not surprisingly, how frequently you update the bitmap. In any case, use MT to test your specific situation both
with and without cacheAsBitmap enabled for DisplayObjects that require bitmap
updates. (It’s a no-brainer when it comes to using cacheAsBitmap for DisplayObjects
that require no bitmap updates: Use it!)
So, if your DisplayObject is a MovieClip with a multiframe timeline with changing
shapes on the different frames, or if you change the scale, skew, alpha, or rotation
of your MovieClip too frequently, enabling the cacheAsBitmap property isn’t helpful
because all those things require an update to the cached bitmap—i.e., the data
describing the bitmap needs to be completely re-written to memory.
If you have a MovieClip
ActionScript, use:
mc
and you want to enable its
cacheAsBitmap
property using
mc.cacheAsBitmap = true;
You can also use the Properties panel to enable the
MovieClips created in the Flash Pro IDE.
cacheAsBitmap
property of
There is one important exception that always makes enabling cacheAsBitmap beneficial
even when changing the scale, skew, alpha, and/or rotation of a DisplayObject (but
not changing frames of a MovieClip). That exception occurs when publishing games
for mobile devices.
Specifically, when publishing for mobile devices, you can enable cacheAsBitmap, assign
the cacheAsBitmapMatrix property of your DisplayObjects, and realize a substantial performance boost (as long as you aren’t changing MovieClip frames):
219
220
Chapter 7 n Optimizing Game Performance
mc.cacheAsBitmap = true;
mc.cacheAsBitmapMatrix = new Matrix();
With both of those properties assigned, you can apply any two-dimensional transform and/or alpha change to mc without causing an update (which would negatively
impact performance) to the cached bitmap.
You don’t have to use the default identity matrix, but there are only a few reasons to
use something other than the default matrix. One would be to save a scaled-down
bitmap of mc to save memory (using a matrix with a=d properties < 1), and another
would be to save a scaled-up bitmap to minimize scaling and aliasing artifacts that
may be caused by changes to mc’s scale, skew, or rotation properties.
Blitting
Stage blitting and partial blitting.
Stage Blitting Stage blitting, short for bit-block transferring, is the use of bitmaps to
render the final display. That is, instead of adding objects like MovieClips, Sprites,
and so on to the display list, pixels are drawn to a stage-sized bitmap, and the bitmap
is added to the stage. To convey animation, the bitmap’s pixels are updated in a loop,
typically an Event.ENTER_FRAME loop using the BitmapData class’s copyPixel() method
applied to the stage-sized bitmap’s bitmapData property using other bitmapData objects
created outside the animation loop.
This technique is more complicated than adding objects directly to the display list,
but it’s much more efficient, often making the difference between unacceptable and
excellent frame rates for a game. To be sure, there is absolutely no reason to use this
unless you need the increased frame rate.
Here’s an example of 10,000 squares moving and rotating across the stage using
MovieClips. This code is in /support files/Chapter 07/blit_test/blit_test_mc.fla.
// fps test comparing movieclips vs blitting.
// On my computer this ran at about 15 frames per second and
// consumed 29.5mb
import flash.events.Event;
import flash.display.MovieClip;
import com.kglad.MT;
MT.init(this,3);
// Then number of square MovieClips
var num:int=10000;
// The length of each MovieClip square
var rectSide:int = 2;
Managing CPU/GPU Usage
// The rate of rotation of each MovieClip
var rotationRate:int = 1;
var mc:MovieClip;
var mcA:Array=[];
movieclipF();
function movieclipF():void {
// Create the MovieClips, assign a speed of 1 or 2, an angle of 0 to 359,
// an initial x and y that places the MovieClip on-stage
for (var i:int=0; i<num; i++) {
mc = new MovieClip();
with (mc.graphics) {
beginFill(0xaa0000);
// Draw square with offset registation point
drawRect(-rectSide/2,-rectSide/2,rectSide,rectSide);
endFill();
}
mc.speed = 1+int(2*Math.random());
mc.angle = int(360*Math.random());
mc.x = int(Math.random()*(stage.stageWidth-mc.width/2));
mc.y = int(Math.random()*(stage.stageHeight-mc.height/2));
addChild(mc);
// Enabling the cacheAsBitmap property is expected to decrease
// performance because these MovieClips are going to be rotated
// in the animation loop. But I was surprised to see how much
// performance suffered when enabling.
mc.mouseEnabled=false;
mc.mouseChildren=false;
mcA.push(mc);
}
this.addEventListener(Event.ENTER_FRAME,animateMovieClipsF);
}
function animateMovieClipsF(e:Event):void {
for (var i:int=0; i<mcA.length; i++) {
mc=MovieClip(mcA[i]);
mc.rotation+=rotationRate;
// Each MovieClip moves along its angle
mc.x+=mc.speed*Math.cos(mc.angle*Math.PI/180);
mc.y+=mc.speed*Math.sin(mc.angle*Math.PI/180);
// If a stage boundary is encountered, the MovieClip ricochets
if (mc.x>stage.stageWidth-mc.width/2) {
mc.angle=180-mc.angle;
} else if (mc.x<mc.width/2) {
mc.angle=180-mc.angle;
} else if (mc.y>stage.stageHeight-mc.height/2) {
221
222
Chapter 7 n Optimizing Game Performance
mc.angle*=-1;
} else if (mc.y<mc.height/2) {
mc.angle*=-1;
}
}
}
We get about 15 fps (frames per second) with that code, which is unacceptable. But
there are a few basic things we can do to improve performance before embarking on
more difficult-to-institute considerations, such as blitting.
Namely, we can reverse those for loops to gain a little performance boost (see the
upcoming “Loops” section), and, more importantly, we can use some constants
instead of recalculating the same values repeatedly.
Here’s the same code using constants instead of calculations in several places and
using reverse for loops. This code is in /support files/Chapter 07/blit_test/blit_
test_mc_basic_optimizations.fla.
// fps test comparing movieclips vs blitting.
// With these constants the coding is actually easier to read and follow.
// 21fps,48mb with mcA an array
// 22fps,48mb with mcA a vector
// 23fps, 28mb with mcA a vector and multiplying by 1 in animateMovieClipsF().
import flash.events.Event;
import flash.display.MovieClip;
import com.kglad.MT;
import flash.geom.Point;
// fps test comparing movieclips vs blitting.
MT.init(this,3);
var num:int=10000;
var rectSide:int = 2;
var rotationRate:int = 1;
var i:int;
// constants for stage width and height. It is faster to repeatedly use stageW
// than to repeatedly use stage.stageWidth
var stageW:int = stage.stageWidth;
var stageH:int = stage.stageHeight;
// constant close enough for mc.width/2 and mc.height/2
var rectHalf:Number = rectSide/2;
// Temporary variable used to calculate vectorX and vectorY below, where
// vectorX is the magnitude of the x increment per loop and vectorY is
// the magnitude of the y increment per loop.
var initialDirection:Number;
// Temporary variable used to calculate vectorX and vectorY below
Managing CPU/GPU Usage
var speed:Number;
var mc:MovieClip;
//var mcA:Array=[];
var mcA:Vector.<MovieClip> = new Vector.<MovieClip>(num);
movieclipF();
function movieclipF():void {
// It is faster to loop from end to start than it is to loop from
// start to end
for(i=num-1;i>=0;i– –){
mc = new MovieClip();
with (mc.graphics) {
beginFill(0xaa0000);
drawRect(-rectHalf,-rectHalf,rectSide,rectSide);
endFill();
}
speed = 1+2*Math.random();
initialDirection = 360*Math.random()*Math.PI/180;
mc.x = int(rectHalf+Math.random()*(stageW-rectSide));
mc.y = int(rectHalf+Math.random()*(stageH-rectSide));
mc.vectorX = speed*Math.cos(initialDirection);
mc.vectorY = speed*Math.sin(initialDirection);
addChild(mc);
mc.mouseEnabled=false;
mc.mouseChildren=false;
mcA[i] = mc;
}
this.addEventListener(Event.ENTER_FRAME,animateMovieClipsF);
}
function animateMovieClipsF(e:Event):void {
for(i=num-1;i>=0;i– –){
mc=MovieClip(mcA[i]);
mc.rotation+=rotationRate;
// This is quirky. If I do not perform some aritmetic operation(s)
// (like multiplying by 1), memory use increases significantly
// because of each of these two lines of code.
mc.x += 1*mc.vectorX;
mc.y += 1*mc.vectorY;
if (mc.x>stageW-rectHalf || mc.x<rectHalf) {
mc.vectorX *= -1;
}
if (mc.y>stageH-rectHalf || mc.y<rectHalf) {
mc.vectorY *= -1;
}
}
}
223
224
Chapter 7 n Optimizing Game Performance
This provides a significant (~40 percent) speed boost to just about acceptable (to our
eyes) frame rates, ~21fps. Now let’s see what blitting can do.
Using stage blitting to encode the same display is more complex, as previously mentioned. The steps involve:
1. Initializing the stage display bitmap assets (Bitmap instance, BitmapData instance,
and Rectangle instance) onto which all the displayed pixels will be copied during
each Event.ENTER_FRAME event loop.
2. Populating a data array with all the data used to update the display. (This isn’t
always necessary.)
3. Populating an array of BitmapData objects. If you had a MovieClip timeline with
animation on it, this is where you would store a BitmapData object of each
MovieClip frame (for example, using a sprite sheet; see www.adobe.com/devnet/
flash/articles/using-sprite-sheet-generator.html). In the test file I created a
BitmapData instance for each angle the rectangles can be rotated using ActionScript.
4. Creating an
Event.ENTER_FRAME
event loop.
5. Updating the data in the Event.ENTER_FRAME loop and copying the appropriate
pixels from the array created in Step 3 to the appropriate location (determined
using the data array from Step 2) of the BitmapData instance created in Step 1.
Here’s the same example with squares moving and rotating across the stage, using
stage blitting and the optimizations used in the second MovieClip example. (See the
test file in /support files/Chapter 07/blit_test_blitting_basic_optimizations.)
// 55fps,6mb with arrays
// 56fps,5.5mb with vectors, no multiplying by 1
// 58+fps, 5.5mb with vectors, multiplying by 1.
import flash.events.Event;
import flash.display.MovieClip;
import com.kglad.MT;
import flash.display.Stage;
// fps test comparing movieclips vs blitting.
// The next 4 lines define same variables as in the MovieClip example
MT.init(this,3);
var num:int=10000;
var i:int;
var stageW:int = stage.stageWidth;
var stageH:int = stage.stageHeight;
var rectSide:int = 2;
var rectHalf:Number = rectSide/2;
Managing CPU/GPU Usage
var rotationRate:int = 1;
// The background color that will be applied to the display bitmap.
var bgColor:uint = 0x000000;
// Display rectangle used to "erase" the previously painted pixels.
var displayBG_Rect:Rectangle = new Rectangle(0,0,stageW,stageH);
// The display’s bitmapData object
var displayBMPD:BitmapData = new BitmapData(stageW,stageH,false,bgColor);
// The display bitmap onto which all pixels will be copied and the only
// thing added to the stage and viewed by the Flash user.
var displayBMP:Bitmap = new Bitmap(displayBMPD);
// Initialize objects needed for steps 2 and 3.
var dataA:Array=[];
var datumA:Array;
var loopN:int
var bmpdA:Array = [];
var bmpdAL:int;
var diagL:Number = Math.sqrt(2*rectSide*rectSide);
var diagLHalf:Number = diagL/2;
var bmpRect:Rectangle=new Rectangle(0,0,diagL,diagL);
var pt:Point = new Point();
var speed:Number;
var initialDirection:Number;
// Populate dataA (step 2).
initDataF();
// Populate bmpdA (step 3).
bitmapDataF();
function initDataF():void {
// Instead of creating num MovieClips with initial positions,
// speeds and angles, I’m creating num arrays (datumA), which
// will contain initial position (a point), and vectorX and
// vectorY (direction of movement). There is no object that
// corresponds to the datumA data. These are abstract quantities
// that will be applied to the bitmapData objects bmpdA (created
// in bitmapF).
for(i=num-1;i>=0;i–){
datumA = [];
// datumA’s first element will be the initial position,
// which will be updated in the Event.ENTER_FRAME loop
datumA.push( new Point( int(Math.random()*(stage.stageWidth-diagL)),
int(Math.random()*(stage.stageHeight-diagL)) ) );
// speed
speed = 1+2*Math.random();
225
226
Chapter 7 n Optimizing Game Performance
// initialDirection used with speed to define vectorX
// and vectoryY. Thereafter, they are not needed.
initialDirection = 2*Math.PI*Math.random();
datumA.push(speed*Math.cos(initialDirection));
datumA.push(speed*Math.sin(initialDirection));
// datumA = [point,vectorX,vectorY]
// Each datumA is added to dataA. DataA serves the
// same purpose as mcA in the MovieClip example.
dataA[i] = datumA;
}
}
// This is where step 3 is done
function bitmapDataF():void{
// I need a temporary display object that I can use to
// populate bmpA. mc will be gc’d because it’s local
// to bitmapF()
var mc:MovieClip = new MovieClip();
with(mc.graphics){
beginFill(0xaa0000);
drawRect(-rectHalf,-rectHalf,rectSide,rectSide);
endFill();
}
// I need a temporary matrix so I can apply a rotation to mc
// when I use the draw() method to transfer mc’s pixels to a
// bitmapData instance. The draw() method creates a bitmapData
// instance of the untransformed object. That is, the object as
// it appears in your library. Any change you want to be seen in
// the bitmapData object has to be applied via one or more of
// the draw() parameters, which include a transform matrix, color
// transform, blend mode, clip rectangle and smoothing.
var mat:Matrix = new Matrix();
for (i=0; i<360; i+=rotationRate) {
// Instantiate a bitmapData instance large enough to
// contain the rotated square
var bmpd:BitmapData = new BitmapData(diagL,diagL,true,0x00ffff00);
// Unapply the previous changes to mat so it can be reused.
mat.identity();
// Apply a rotation to mat
mat.rotate(i*Math.PI/180);
// Apply a translation to mat so mc is positioned in the
// center of bmpd
mat.tx+=diagLHalf;
mat.ty+=diagLHalf;
// Apply the draw() method using mat to make the square
Managing CPU/GPU Usage
// appear rotated.
bmpd.draw(mc,mat);
// Add this bitmapData instance to bmdA so it can be used
// in our Event.ENTER_FRAME loop.
bmpdA[i] = bmpd;
}
bmpdAL = bmpdA.length
addChild(displayBMP);
// loopN used to count number of Event.ENTER_FRAME loops and
// choose which bitmap to display. ie, which rotation.
loopN = 0;
// step 4.
this.addEventListener(Event.ENTER_FRAME,animateBitmapsF);
}
function animateBitmapsF(e:Event=null):void {
// Applying the lock() method stops the bitmap from being
// updated while its bitmapData object is being updated.
// I’ll unlock when all the bitmapData changes are completed
// so the bitmap can then be updated.
displayBMPD.lock();
// This is where the pixels in displayBMPD are "erased". Except,
// they are not erased. They are all colored bgColor, which is
// the background color I chose.
displayBMPD.fillRect(displayBG_Rect,bgColor);
// This code is similar to the MovieClip example except instead
// of using mcA to update MovieClips, I am using dataA and
// updating data.
for(i=num-1;i>=0;i–){
pt = Point(dataA[i][0]);
// This is quirky. If I do not perform some arithmetic
// operation(s) (like multiplying by 1), memory use
// increases significantly because of each of these two
// lines of code.
pt.x += 1*dataA[i][1];
pt.y += 1*dataA[i][2];
if (pt.x>stageW-diagL || pt.x<0) {
dataA[i][1] *= -1;
}
if (pt.y>stageH-diagL|| pt.y<0) {
dataA[i][2] *= -1;
}
// During each for-loop iteration, pixels are copied from
// bmpdA to displayBMPD. You can see how loopN is used to
// make it appear that squares are rotating and you can see
227
228
Chapter 7 n Optimizing Game Performance
// how pt is used to make it appear as if each square is
// also moving across the stage.
displayBMPD.copyPixels(bmpdA[loopN%bmpdAL],bmpRect,pt);
}
loopN++;
// all the pixels, for this Event.ENTER_FRAME loop iteration, have
// been applied to displayBMPD. Finally, apply the unlock() method
// so the bitmap corresponding to this bitmapData object is updated.
displayBMPD.unlock();
}
We set the frame rate at 60 to get a better idea of how much improvement we can
see with blitting. The second MovieClip version plays at 21 fps (and 48 MB memory
use), while the blitting version plays at 55 fps (and 6 MB memory use).
So, the frame rate almost tripled with blitting, and far less memory was used. More
importantly, the MovieClip version fails an eyeball test, while the blitting version
passes with what appears to be very smooth animation.
The downside to stage blitting, other than the difficulty coding, is that it may consume a large amount of memory to create the needed bitmaps. That is a significant
factor when you’re creating a game for a device like the iPad that has high screen
resolution (1024 × 768 for the first- and second-generation iPads, and 2048 × 1536
for the third-generation iPad) and relatively low memory (RAM) capacity (256 MB,
512 MB, and 1 GB for first-, second-, and third-generation iPads, respectively).
In the Stage3D section, we’ll see how easy it is to consume large amounts of memory
when using blitting in an effort to decrease CPU use. This will add more support to
the memory versus CPU load tradeoff mentioned at the start of this chapter.
Your game should consume no more than half the available RAM, and that includes
not just RAM used by bitmaps but everything else in your game that consumes
RAM.
Partial Blitting As the name implies, partial blitting combines the use of the Flash
display list and copying pixels to BitmapData objects. Typically, each object displayed
on-stage is a bitmap that is added to the display list and manipulated like you’re used
to doing with display objects like MovieClips. Each object’s animation is blitted to an
array of BitmapData objects.
For example, using the previous example of squares rotating and moving across the
stage, we can blit the squares and their various rotations, store those BitmapData
objects in bmpdA, add bitmaps to the display list, manipulate the bitmaps just like
any display object (in other words, like the above MovieClips) in the Event.
Managing CPU/GPU Usage
loop, and finally assign the
appropriate bmpdA element.
ENTER_FRAME
bitmapData
property of the bitmaps to the
// 26 fps, 75mb with vectors
// 24 fps, 75mb with arrays
// fps test partial blitting
import flash.events.Event;
import flash.display.MovieClip;
import com.kglad.MT;
import flash.display.Bitmap;
import flash.display.BitmapData;
MT.init(this,3);
var num:int=10000;
var i:int;
var rectSide:int = 2;
var rectHalf:Number = rectSide/2;
var rotationRate:int = 1;
var dataA:Vector.<Array> = new Vector.<Array>(num);
//var dataA:Array = new Array(num);
var datumA:Array;
var loopN:int
var bmpdA:Vector.<BitmapData> = new Vector.<BitmapData>(int(360/rotationRate));
//var bmpdA:Array = new Array(int(360/rotationRate));
var diagL:Number = Math.sqrt(2*rectSide*rectSide);
var diagLHalf:Number = diagL/2;
var bmpdAL:int = bmpdA.length;
var stageW:int = stage.stageWidth;
var stageH:int = stage.stageHeight;
var speed:Number;
var initialDirection:Number;
initF();
bitmapDataF();
// The only significant change here is datumA[0] is a Bitmap.
// Its bitmapData property will be assigned in animateBitmapsF().
function initF():void {
for (i=num-1;i>=0;i– –) {
datumA = [];
// Create the Bitmaps that will be added to the
// display list
datumA[0] = new Bitmap(null,"auto",true);
// Add to the display list
addChild(datumA[0]);
// Assign initial positions
datumA[0].x = int(Math.random()*(stageW-diagL));
229
230
Chapter 7 n Optimizing Game Performance
datumA[0].y = int(Math.random()*(stageH-diagL));
// use speed and initialDirection to define
// vectorX and vectorY
speed = 1+2*Math.random();
initialDirection = 2*Math.PI*Math.random();
// store vectorX and vectorY
datumA[1] = speed*Math.cos(initialDirection);
datumA[2] = speed*Math.sin(initialDirection);
dataA[i] = datumA;
}
}
// No significant change in bitmapDataF()
function bitmapDataF():void{
var mc:MovieClip = new MovieClip();
with(mc.graphics){
beginFill(0xaa0000);
drawRect(-rectHalf,-rectHalf,rectSide,rectSide);
endFill();
}
var mat:Matrix = new Matrix();
for (i=0; i<360; i+=rotationRate) {
var bmpd:BitmapData = new BitmapData(diagL,diagL,true,0x00ffff00);
mat.identity();
mat.rotate(i*Math.PI/180);
mat.tx+=diagLHalf;
mat.ty+=diagLHalf;
bmpd.draw(mc,mat);
bmpdA[i] = bmpd;
}
loopN = 0;
this.addEventListener(Event.ENTER_FRAME,animateBitmapsF);
}
function animateBitmapsF(e:Event=null):void {
for (i=num-1;i>=0;i– –) {
// Update the Bitmaps’ positions directly
// Multiplying by 1 makes no difference here.
dataA[i][0].x += dataA[i][1];
dataA[i][0].y += dataA[i][2];
if (dataA[i][0].x>stageW-diagL || dataA[i][0].x<0) {
dataA[i][1] *= -1;
}
if (dataA[i][0].y>stageH-diagL || dataA[i][0].y<0) {
dataA[i][2]*=-1;
}
Managing CPU/GPU Usage
// Assign the bitmapData property for each Bitmap.
dataA[i][0].bitmapData = bmpdA[loopN%bmpdA.length];
}
loopN++;
}
For this example, partial blitting isn’t nearly as fast as stage blitting when tested on
my PC. But keep an open mind, because partial blitting may be faster than stage blitting in some situations. In addition, it’s easier to code partial blitting than stage blitting, so if you can achieve acceptable frame rates with partial blitting, that would
eliminate the additional work needed for stage blitting.
Loops
Event.ENTER_FRAME, for, while and do loops.
Event.ENTER_FRAME Loops
In the previous chapter, we found that creating multiple Event.ENTER_FRAME listeners
applied to one instance calling multiple listener functions was slightly more efficient
than creating one Event.ENTER_FRAME listener calling one listener function, which then
called other functions.
However, it’s a different situation when you have multiple objects, each with added
Event.ENTER_FRAME listeners, compared to one Event.ENTER_FRAME listener. There is
about a twofold performance gain using one object with Event.ENTER_FRAME listeners
compared to using many objects with Event.ENTER_FRAME listeners. (See the enterFrame_
test_one_v_many_loops_with_different_movieclips folder.)
For example, in the tank combat game, all of our tanks had their own Event.ENTER_FRAME listeners. This is less efficient than having one object with Event.ENTER_FRAME
listeners updating all the tanks and shells.
For Loops, While Loops, and Do Loops
The bottom line on fast-executing loops in Flash is that reverse for loops are the fastest. If a stored list of same-type objects is needed in the loop, a reverse for loop using
a vector to reference the list of objects is fastest.
All three loops execute faster if you use an int for the iteration parameter than if you
use a uint. All three loops execute faster if you decrement the loop variable than if
you increment it. (Note: If you decrement a loop variable i and use i>=0 as the terminal condition, you will trigger an endless loop if i is a uint.)
231
232
Chapter 7 n Optimizing Game Performance
All three loops execute faster if you use a variable or constant for the terminal condition rather than an expression or object property. For example:
For(var i:int=0;i<some_array.length;i++){
}
is slower than:
Var len:int = some_array.length;
for(var i:int=0;i<len;i++){
//
}
because Flash must evaluate the terminal condition with each loop iteration.
Because the initial condition only needs to be evaluated once (and not with each loop
iteration), there is no significant difference whether you use an expression or object
property for the initial condition in a for loop.
You should move anything outside a loop that can be moved without affecting the
result. That includes declaring objects outside the loop (see “Object Reuse” within
the “Objects” section), where using the new constructor inside a loop sometimes
can be moved outside the loop and the terminal loop condition, if it is an expression,
should be evaluated outside the loop.
Again, reverse for loops are much faster than while loops and do loops. Using some
simple tests, I found a reverse for loop to be 4 to 10 times faster than comparable
while and do loops. (See /support files/Chapter 07/loops/for_v_do_v_while_loops.fla.)
var i:int;
var n:int = 100000000;
var startTime:int = getTimer();
/*
// 4162
i=n;
while(i>=0){
i–;
}
*/
///*
// 4177
i = n;
do {
i–;
}while(i>=0);
Managing CPU/GPU Usage
//*/
/*
// 434
for(i=n-1;i>=0;i– –){
//
}
*/
trace(getTimer()-startTime);
I have seen some mention that using objects that each reference the next object is
faster than using an array to reference the objects. For example, the following creates
a sequence of MovieClips that allows referencing all n MovieClips by starting with
last_mc and using the prev property to iterate through them all.
In my tests I found that to be false. Using an array was easier and faster both to initialize and to use. Using a vector instead of an array, of course, was still faster. (See
the test file in /support files/Chapter 07/loops/for_loop_v_sequential_loop.)
var n:int = 500000;
var i:int;
var mc:MovieClip;
var last_mc:MovieClip;
var prev_mc:MovieClip;
for(i=n-1;i>=0;i– –){
mc=new MovieClip();
if(i==n-1){
last_mc = mc;
} else {
prev_mc.prev = mc;
}
prev_mc = mc;
}
None of these suggestions is likely to make a major difference under most conditions,
but they’re worth knowing if you’re trying to squeeze every bit of efficiency out of
your coding or you need to iterate through a large number of loops.
For example, in the blitting example, using constants instead of recalculating array
lengths and iterating from end to start made no difference in the MovieClip frame
rate but did increase the blitting frame rate by about 10 percent.
Mouse Interactivity
MovieClips
and Sprites can interact with the mouse. Even when you don’t code for
any mouse interactivity, the Flash Player checks for mouse interactions. You can
233
234
Chapter 7 n Optimizing Game Performance
save some CPU cycles by disabling mouse interactivity for those objects that do not
need to interact with the mouse.
This can be a major help if you see a performance problem (or you detect that one of
your computer’s fans increases speed) when your mouse moves across the stage. Disabling mouse interactivity will improve performance and quiet your computer fan(s).
For example, none of my tanks interacted with the mouse, so, in the
could use:
Tank
class, I
// disables mouse interactivity for the tank
this.mouseEnabled = false;
// disables mouse interactivity for all children of the tank
this.mouseChildren = false.
I saw the frame rate increase by about 2 1/2 times when disabling all MovieClips in a
test file. This code is in /support files/Chapter 07/mouse_interactivity.
import com.kglad.MT;
import flash.display.MovieClip;
// fs test comparing movieclips with and without mouse enabling.
MT.init(this,3);
var num_obj:int=100000;
// mc’s mouse enabled: fps~55 with no mouse movement.
// fps<20 with mouse movement.
// mc’s mouse disabled: fps~55 with no mouse movement.
// fps~35 with mouse movement.
// mc’s mouse disabled and main movieclip disabled:
// fps~55 with no mouse movement.
// fps~50 with mouse movement.
var mouseEnable:Boolean=false;
this.mouseEnabled=mouseEnable;
this.mouseChildren=mouseEnable;
for (var i:int=0; i<num_obj; i++) {
var mc:MovieClip = new MovieClip();
addChild(mc);
graphicF(mc);
childrenF(mc);
}
function graphicF(mc:MovieClip):void {
with (mc.graphics) {
beginFill(0xffffff*Math.random());
drawRect(0,0,Math.ceil(2000/num_obj),Math.ceil(2000/num_obj));
}
Managing CPU/GPU Usage
mc.x=stage.stageWidth*Math.random();
mc.y=stage.stageHeight*Math.random();
}
function childrenF(mc:MovieClip):void {
var c:MovieClip = new MovieClip();
mc.addChild(c);
graphicF(c);
mc.mouseEnabled=mouseEnable;
mc.mouseChildren=mouseEnable;
}
Remove Event Listeners
Even though the later Flash Player versions appear to remove listeners when objects
are gc’d and having strong listeners doesn’t appear to delay gc’ing, you should still
explicitly remove all event listeners as soon as possible. The sooner a listener is
removed, the fewer CPU cycles are consumed in dealing with the listener. In addition, you may not know which Flash Player version a user has unless you publish
for a mobile and package Air with your game. Some Player versions may not gc
objects that have even weak listeners.
In other words, don’t count on the Flash Player to save you from poor coding. Just
like the problem I had with tank combat, listeners can continue to exist, cause havoc,
and consume CPU cycles even when the listener is applied to an object that is ready
for gc.
Stage3D
is a GPU-enabled display-rendering model that is new with Flash Player 11.
This model was designed to facilitate 3D rendering but reportedly can be advantageously used for 2D displays, too.
Stage3D
Because display rendering typically has been handled by the CPU, which also does all
the other work needed to run a game, harnessing the power of the GPU for rendering and freeing the CPU to do all the other work should significantly improve performance on devices with capable GPUs.
To view Stage3D content, you will need to use Flash Player 11 or better. To use the
Stage3D API, you will need to publish for Flash Player 11 or better. If you have Flash
Pro CS6, you’re set. If you have Flash Pro CS5 or CS5.5, you can update your Flash
Pro so you can publish to Flash Player 11. See http://blogs.adobe.com/rgalvan/2011/
11/adding-fp11-support-to-flash-pro-cs5-and-cs5-5.html.
235
236
Chapter 7 n Optimizing Game Performance
Unfortunately, using the Stage3D API is laborious and difficult. However, there are
several free public frameworks available that handle the low-level laborious code
needed to use Stage3D, and they all offer easier-to-use APIs.
One of these frameworks, Starling (http://gamua.com/starling), is designed for 2D
games. It is easy to use and effectively abstracts the complexity of Stage3D by supplying its own API (http://doc.starling-framework.org/core).
I used Starling to see how it compared to blitting and partial blitting. In some situations it performed worse than both of those. In fact, it performed much worse than
the unoptimized 10,000-square MovieClip test.
However, unchecking Permit Debugging in the Starling test more than doubled the
frame rate and was comparable to the unoptimized 10,000-square MovieClip test.
That is still a disappointment, but part of the problem is that I used the debug version of the Flash Player, and Starling appears to perform much worse in the debug
versus non-debug version of the Flash Player.
In any case, although Starling fares poorly when compared to blitting in the 10,000
MovieClip test, I believe that’s because the 10,000 MovieClip test is particularly
amenable to blitting. It may not be that Starling does badly at rendering the 10,000
MovieClip test.
If you’re using many MovieClips that each contain a timeline with animation, Starling
will almost certainly outperform anything you can build that utilizes simple
optimizations.
In addition, although blitting may provide the performance needed to meet or exceed
the benefits of using Stage3D and Starling, blitting may not be practical because of the
memory required to create the needed bitmaps.
After the comparison of blitting and Starling using the 10,000 MovieClip test, I will
apply blitting to animations from two Starling tests that I found online. These tests
show substantial memory problems when applying blitting to those two animations.
To use Starling, you need to download the starling.swc (http://gamua.com/starling/
download) and add it to your Library path by first clicking File > Publish Settings >
ActionScript Settings. (See Figure 7.1.)
Managing CPU/GPU Usage
Action script
settings icon
Figure 7.1
Click the ActionScript Settings icon to open the Advanced ActionScript Settings 3.0 panel, where you can add
an SWC to your library path.
Source: Adobe Systems Incorporated.
Then click the Library Path tab and Browse to SWC File icon. (See Figures 7.2 and 7.3.)
237
238
Chapter 7 n Optimizing Game Performance
Library
Path tab
Figure 7.2
Click the Advanced ActionScript Settings 3.0 Settings Library Path tab.
Source: Adobe Systems Incorporated.
Managing CPU/GPU Usage
Browse to
SWC File icon
Figure 7.3
Click the Browse to SWC File icon to open an Open File window.
Source: Adobe Systems Incorporated.
239
240
Chapter 7 n Optimizing Game Performance
Figure 7.4
Open File window.
Source: © 2013 Keith Gladstien, All Rights Reserved.
With the Open File window open, navigate to your downloaded starling.swc, click it, and
then click Open. That should add starling.swc to your Library Path (see Figure 7.5).
Managing CPU/GPU Usage
Confirmation that
starling.swf has
been added to your
Library Path.
Figure 7.5
After clicking on starling.swc, you should see it added to your Library Path.
Source: Adobe Systems Incorporated.
Finally, click OK to close the Advanced ActionScript 3.0 Settings panel and click OK
again to close the Publish Settings panel. Save your FLA, and you’re ready to use the
Starling API.
I created a document class, Main, that passes a class (Game) to Starling. In Game, I create
10,000 rectangles. Only they are called quads in Starling, and I created a QuadSprite
class to create those quads.
241
242
Chapter 7 n Optimizing Game Performance
The test files are in /support files/Chapter 07/starling_test/test1. There are two test
files in that directory, starling_test.fla and starling_test2.fla. The only difference is
that starling_test.fla uses Main.as, which initializes its Starling instance using Game.
as, while starling_test2.fla uses Main1.as, which initializes its Starling instance using
Game1.as.
The only difference between Game.as and Game1.as is the application of touchable
and blendMode properties directly to the quads (Game.as) and parent sprite (Game1.
as) in an attempt to increase performance. I found no significant benefit of one over
the other.
Starling Test with 10,000 MovieClips
com.kglad.Main
package com.kglad{
import flash.system.ApplicationDomain;
import flash.display.Sprite;
import starling.core.Starling;
public class Main extends Sprite {
private var starlingInst:Starling;
public function Main() {
// Check for Flash Player 11
if (ApplicationDomain.currentDomain.hasDefinition("flash.display.Stage3D")){
// It’s present so we can use Stage3D
// and Starling
startF();
} else {
// Start fallback code if hardware acceleration is
// not available
}
}
private function startF():void{
// I ran into this problem while profiling this test.
// If your game loses focus, for example, by opening
// the CPU profiler, you get an error message when
// returning to your game unless you take steps to
// handle the lost context. Well, there are a few
// steps to take in Stage3D, but in Starling there’s
// just one thing to do. Enable the static
// handleLostContext property.
Starling.handleLostContext = true;
// Create Starling instance by passing a first
Managing CPU/GPU Usage
// parameter that is a class that extends a Starling
// DisplayObject. This class is instantiated and
// added to the Starling stage. The second
// parameter that must be passed is a Flash
// stage reference.
starlingInst = new Starling(Game,stage);
// Starling has a built-in frame rate display.
starlingInst.showStats = true;
// set anti-aliasing (higher is better quality
// but slower performance)
starlingInst.antiAliasing=0;
starlingInst.enableErrorChecking = true;
// Start the Starling instance
starlingInst.start();
}
}
}
com.kglad.Game
package com.kglad{
// import the needed Starling classes
import starling.display.Quad;
import starling.display.Sprite;
import starling.events.Event;
import starling.utils.deg2rad;
import starling.display.BlendMode;
public class Game extends Sprite {
private var quad:QuadSprite;
private var pSprite:Sprite;
private var num:int = 10000;
private var i:int;
private var rectSize:int = 2;
private var rotationRate:Number = deg2rad(1);
private var quadA:Vector.<QuadSprite> = new Vector.<QuadSprite>(num);
private var stageW:int;
private var stageH:int;
private var rectHalf:Number = rectSize/2;
private var speed:Number;
private var quadVector:Vector.<Vector.<Number>> = new
Vector.<Vector.<Number>>(num);
public function Game() {
// no weak listeners in starling
addEventListener(Event.ADDED_TO_STAGE,init);
243
244
Chapter 7 n Optimizing Game Performance
}
private function init( e:Event ):void {
removeEventListener(Event.ADDED_TO_STAGE,init);
stageW = stage.stageWidth;
stageH = stage.stageHeight;
createQuadsF();
addEventListener(Event.ENTER_FRAME,animateQuadsF);
}
private function createQuadsF():void{
// create a parent starling sprite in case I want
// to add TouchEvents. Instead of adding many
// TouchEvents (one to each child), I’ll just
// add one to the parent and, in the event
// listener, detect which child was touched.
parentF();
for(i=num-1;i>=0;i– –){
// A starling sprite that contains a quad.
quad = new QuadSprite(rectSize,0xffffff*Math.random());
quadA[i] = quad;
// quad rotation, speed, vectorX, vectorY
// Starling DisplayObjects have rotations in
// radians, not degrees
// Because QuadSprites look the same when
// rotated pi/2 and pi etc, I may as well
// use a rotation that requires the
// least computation.
quad.rotation = randomF(0,Math.PI);
speed = randomF(1,3);
//quad.vectorX = speed*randomF(-1,1);
//quad.vectorY = speed*randomF(-1,1);
quadVector[i] = new <Number>[speed*randomF
(-1,1),speed*randomF(-1,1)];
quad.blendMode = BlendMode.NONE;
//quad.flatten();
quad.touchable = false
pSprite.addChild(quad);
quad.x = int(randomF(rectHalf,stageW-rectSize));
quad.y = int(randomF(rectHalf,stageH-rectSize));
}
//pSprite.flatten();
}
private function animateQuadsF(e:Event):void{
//pSprite.unflatten();
for(i=num-1;i>=0;i– –){
quadA[i].rotation += rotationRate;
Managing CPU/GPU Usage
//quadA[i].x += quadA[i].vectorX;
//quadA[i].y += quadA[i].vectorY;
quadA[i].x += quadVector[i][0];
quadA[i].y += quadVector[i][1];
boundaryF(quadA[i],i);
}
//pSprite.flatten();
}
private function boundaryF(q:QuadSprite,i:int):void{
if (q.x>stageW-rectHalf || q.x<rectHalf) {
quadVector[i][0] *= -1;
}
if (q.y>stageH-rectHalf || q.y<rectHalf) {
quadVector[i][1] *= -1;
}
}
function parentF():void{
pSprite = new Sprite();
var pQuad:Quad = new Quad(stageW,stageH,0x000000);
pQuad.blendMode = BlendMode.NONE;
addChild(pSprite);
pSprite.addChild(pQuad);
}
private function randomF(int1:int,int2:int):Number{
return int1+(int2-int1)*Math.random();
}
}
}
com.kglad.QuadSprite
package com.kglad {
// import the needed Starling classes
import starling.display.Quad;
import starling.display.Sprite;
// Extend the Starling Sprite
public class QuadSprite extends Sprite {
public function QuadSprite(size:int,color:uint) {
var q:Quad = new Quad(size,size);
q.pivotX = size/2;
q.pivotY = size/2;
q.color = color;
addChild( q );
}
}
}
245
246
Chapter 7 n Optimizing Game Performance
If you publish a mobile air game, set the Render mode to Direct (see Figure 7.6). If
you publish embedding HTML, set the Window mode to Direct (see Figure 7.7).
Render Mode
combobox showing
Direct is selected
Figure 7.6
For a mobile air game, click File > Publish Settings > Player Settings and select Direct from the Render Mode
combobox.
Source: Adobe Systems Incorporated.
Managing CPU/GPU Usage
Window Mode
combobox showing
direct is selected
Figure 7.7
For a web-based game SWF that will be embedded in an HTML file, click File > Publish Settings > HTML
Wrapper and select Direct from the Window Mode combobox.
Source: Adobe Systems Incorporated.
Starling Test with Eric Smith’s Patch the Scarecrow
This test shows the drawback of blitting: large memory requirements. Using blitting
to display a MovieClip with different rotations and different alpha values requires:
(totalFrames × total rotations × total alphas) bitmaps
If you’re going to display 360 rotations and 100 alphas, for a 10-frame 100-pixel ×
100-pixel MovieClip, that would be:
~10 (frames) × 360 (rotations) × 100 (alphas) × 20,000 (pixels) × 32bits/pixel =
~27,466 MB
247
248
Chapter 7 n Optimizing Game Performance
That is almost 27 gigabytes of memory, which exceeds the RAM of most desktops.
And that’s just one MovieClip.
In addition, it can take an appreciable amount of time to create the needed bitmaps.
So, some projects cannot use blitting. Even though it’s not necessary to use 360 rotations (I used 120 in this example) and 100 different alphas (I used 26 in this example), I still used more memory than is available on even a third-generation iPad. The
upside is that the example does run much faster on my desktop than Starling.
This test is in /support files/Chapter 07/starling_test/test2/StarlingTest_blitting1.fla
and compares Aymeric Lamboley’s Starling test at www.aymericlamboley.fr/blog/
starling-performance-test with blitting.
This test uses Patch the Scarecrow from Citrus Engine’s Eric Smith (http://
citrusengine.com).
import flash.display.MovieClip;
import flash.geom.Point;
import flash.display.Bitmap;
import com.kglad.MT;
import flash.geom.Matrix;
import flash.geom.ColorTransform;
MT.init(this,3);
// num=600: fps ~60, memory ~1389mb
// num=900: fps ~54
// num=1200:fps ~41
var bgColor:uint = 0x999999;
// display bitmap where "frame" will be drawn.
var displayBG_Rect:Rectangle = new Rectangle(0,0,stage.stageWidth,
stage.stageHeight);
var displayBMPD:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,
false,bgColor);
var displayBMP:Bitmap = new Bitmap(displayBMPD);
var dataA:Array=[];
var datumA:Array;
var loopN:int
var bmpdA:Array = [];
var bmpRect:Rectangle;
var num:int = 600;
var patchW:Number;
var patchH:Number;
var bmpdALength:int;
var stageW:int = stage.stageWidth;
var stageH:int = stage.stageHeight;
Managing CPU/GPU Usage
var deg2Rad:Number = Math.PI/180;
var bmpd:BitmapData;
var diagL:Number;
var mat:Matrix;
var pt:Point = new Point();
var frameN:int;
var rotationN:int;
var alphaN:int;
var rotationInc:int = 3;
var alphaInc:int = 4;
var rotationNum:int = 360/rotationInc;
var alphaNum:int = 1+100/alphaInc;
bitmapDataF();
init();
function bitmapDataF():void{
var patch:MovieClip = new Patch();
patchW = patch.width;
patchH = patch.height;
diagL = Math.sqrt(patchW*patchW+patchH*patchH);
bmpRect = new Rectangle(0,0,diagL,diagL);
mat = new Matrix();
var ct:ColorTransform = new ColorTransform();
//var bmpdNum:int = 0;
var patchTF:int = patch.totalFrames;
for(frameN=0; frameN<patchTF; frameN++) {
if(frameN>0){
patch.gotoAndStop(frameN+1);
}
bmpdA[frameN] = [];
for(rotationN=0;rotationN<360;rotationN+=rotationInc){
bmpdA[frameN][rotationN] = [];
adjustF(rotationN*deg2Rad);
for(alphaN=0;alphaN<=100;alphaN+=alphaInc){
//bmpdNum++;
bmpd = new BitmapData(diagL,diagL,true,0x00ffff00);
ct.alphaMultiplier = alphaN/100;
bmpd.draw(patch,mat,ct);
bmpdA[frameN][rotationN][alphaN] = bmpd;
}
}
}
//trace("bmpdNum: ",bmpdNum,bmpdNum*diagL*diagL*4/(1024*1024));
249
250
Chapter 7 n Optimizing Game Performance
addChild(displayBMP);
// loopN used to count number of enterframe loops and
// choose which bitmap to display. ie, which patchMovieClip frame.
loopN = 0;
}
function adjustF(r:Number):void{
mat.identity();
mat.rotate(r);
mat.tx = diagL/2;
mat.ty = diagL/2;
}
function init():void {
bmpdALength = bmpdA.length;
for (var i:int=num-1;i>=0; i–) {
// = [initial point, speed, initial direction,
// initial patchMovieClip frame
// index]
///*
dataA[i] = int(bmpdALength*Math.random());
//*/
}
this.addEventListener(Event.ENTER_FRAME,animateBitmapsF);
}
function animateBitmapsF(e:Event):void {
displayBMPD.lock();
displayBMPD.fillRect(displayBG_Rect,bgColor);
for(i=num-1;i>=0;i– –){
///*
pt.x = -diagL/2+int(stageW*Math.random());
pt.y = -diagL/2+int(stageH*Math.random());
frameN = (loopN+dataA[i])%bmpdALength;
rotationN = rotationInc*int(rotationNum*Math.random());
alphaN = alphaInc*int(alphaNum*Math.random());
//*/
displayBMPD.copyPixels(bmpdA[frameN][rotationN][alphaN],bmpRect,pt);
}
loopN++;
displayBMPD.unlock();
}
Starling Test with Chris Georgenes’ Mudbubble Boy
This is another test comparing Starling with blitting. The Starling test is here:
www.kouma.fr/lab/StarlingStressTest and uses Chris Georgenes’ Mudbubble Boy
(www.mudbubble.com).
Managing CPU/GPU Usage
This test shows an additional potential issue with blitting. The copyPixels method’s
speed is inversely related to the size of the bitmaps being copied. You can test that by
changing the definition of boyW and/or boyH (for example, boyW=boy.width/2).
Also, with boy’s diagL ~333 px and boy having 15 frames, I had to decrease the rotations and alpha to prevent an Invalid BitmapData error.
I was trying to use 15 frames × 120 rotations × 26 alphas × 333 px × 333 px × 32bits/
px > 19 GB, which exceeded my 12 GB of RAM (of which only 3 MB is usable by
Flash Pro CS6). And, if I tried to incorporate scaling, even more memory would have
been required.
Using 12 rotations instead of 120 decreased memory usage by 1/10 to less than 2 GB
of boy bitmaps and avoided the Invalid BitmapData error.
This test is in /support files/Chapter 07/starling_test/test2/StarlingTest_blitting2.fla.
import flash.display.MovieClip;
import flash.geom.Point;
import flash.display.Bitmap;
import com.kglad.MT;
import flash.geom.Matrix;
import flash.geom.ColorTransform;
MT.init(this,3);
// num=600: FPS: 24 || Memory Use: 2015.79 MB
var bgColor:uint = 0x999999;
// display bitmap where "frame" will be drawn.
var
displayBG_Rect:Rectangle
=
new
Rectangle(0,0,stage.stageWidth,stage.
stageHeight);
var displayBMPD:BitmapData = new BitmapData(stage.stageWidth,stage.
stageHeight,false,bgColor);
var displayBMP:Bitmap = new Bitmap(displayBMPD);
var dataA:Array=[];
var datumA:Array;
var loopN:int
var bmpdA:Array = [];
var bmpRect:Rectangle;
var num:int = 600;
var boyW:Number;
var boyH:Number;
var bmpdALength:int;
var stageW:int = stage.stageWidth;
var stageH:int = stage.stageHeight;
var deg2Rad:Number = Math.PI/180;
var bmpd:BitmapData;
251
252
Chapter 7 n Optimizing Game Performance
var diagL:Number;
var mat:Matrix;
var pt:Point = new Point();
var frameN:int;
var rotationN:int;
var alphaN:int;
var rotationInc:int = 30;
var alphaInc:int = 4;
var rotationNum:int = 360/rotationInc;
var alphaNum:int = 1+100/alphaInc;
bitmapDataF();
init();
function bitmapDataF():void{
var boy:MovieClip = new Boy();
boyW = boy.width;
boyH = boy.height;
diagL = Math.ceil(Math.sqrt(boyW*boyW+boyH*boyH));
bmpRect = new Rectangle(0,0,diagL,diagL);
mat = new Matrix();
var ct:ColorTransform = new ColorTransform();
var bmpdNum:int = 0;
var boyTF:int = boy.totalFrames;
for(frameN=0; frameN<boyTF; frameN++) {
if(frameN>0){
boy.gotoAndStop(frameN+1);
}
bmpdA[frameN] = [];
for(rotationN=0;rotationN<360;rotationN+=rotationInc){
bmpdA[frameN][rotationN] = [];
adjustF(rotationN*deg2Rad);
for(alphaN=0;alphaN<=100;alphaN+=alphaInc){
bmpdNum++;
bmpd = new BitmapData(diagL,diagL,true,0x00ffff00);
ct.alphaMultiplier = alphaN/100;
bmpd.draw(boy,mat,ct);
bmpdA[frameN][rotationN][alphaN] = bmpd;
}
}
}
//trace("bmpdNum: ",bmpdNum,bmpdNum*diagL*diagL*4/(1024*1024));
addChild(displayBMP);
// loopN used to count number of enterframe loops and choose
Managing CPU/GPU Usage
// which bitmap to display. ie, which boyMovieClip frame.
loopN = 0;
}
function adjustF(r:Number):void{
mat.identity();
mat.rotate(r);
mat.tx = diagL/2;
mat.ty = diagL/2;
}
function init():void {
bmpdALength = bmpdA.length;
for (var i:int=num-1;i>=0; i–) {
// = [initial point, speed, initial direction,
// initial boyMovieClip frame
// index]
///*
dataA[i] = int(bmpdALength*Math.random());
//*/
}
this.addEventListener(Event.ENTER_FRAME,animateBitmapsF);
}
function animateBitmapsF(e:Event):void {
displayBMPD.lock();
displayBMPD.fillRect(displayBG_Rect,bgColor);
for(i=num-1;i>=0;i– –){
///*
pt.x = -diagL/2+int(stageW*Math.random());
pt.y = -diagL/2+int(stageH*Math.random());
frameN = (loopN+dataA[i])%bmpdALength;
rotationN = rotationInc*int(rotationNum*Math.random());
alphaN = alphaInc*int(alphaNum*Math.random());
//*/
displayBMPD.copyPixels(bmpdA[frameN][rotationN][alphaN],bmpRect,pt);
}
loopN++;
displayBMPD.unlock();
}
You can avoid excessive memory requirements because of needed rotations and
alphas by using partial blitting because the rotations and alphas are applied directly
to the bitmaps, avoiding the need for the additional BitmapData objects. Also, each of
the BitmapData objects used is much smaller because it doesn’t need to account for the
rotations.
However, partial blitting isn’t nearly as efficient as blitting, so tests using Patch and
Mudbubble Boy show the benefit of using Starling over both blitting techniques.
253
254
Chapter 7 n Optimizing Game Performance
The partial blitting test using Patch is in /support files/Chapter 07/starling_test/test2/
StarlingTest_partial_blitting.fla, and the partial blitting test using Mudbubble Boy is
in /support files/Chapter 07/starling_test/test2/StarlingTest_partial_blitting1.fla. Here
is the code using Mudbubble Boy.
import flash.display.MovieClip;
import flash.geom.Point;
import flash.display.Bitmap;
import com.kglad.MT;
import flash.geom.Matrix;
MT.init(this,1);
// FPS: 1 || Memory Use: 14.89 MB
var dataA:Array=[];
var datumA:Array;
var loopN:int
var bmpdA:Array = [];
var bmpRect:Rectangle;
var num:int = 600;
var boyW:Number;
var boyH:Number;
var bmpdALength:int;
var stageW:int = stage.stageWidth;
var stageH:int = stage.stageHeight;
bitmapDataF();
init();
function bitmapDataF():void{
var boy:MovieClip = new Boy();
boyW = boy.width;
boyH = boy.height;
//bmpRect = new Rectangle(0,0,boyW,boyH);
var mat:Matrix = new Matrix();
mat.tx = boyW/2;
mat.ty = boyH/2;
for (var i:int=0; i<boy.totalFrames; i++) {
boy.gotoAndStop(i);
var bmpd:BitmapData = new BitmapData(boyW,boyH,true,0x00ff0000);
bmpd.draw(boy,mat);
bmpdA[i] = bmpd;
}
bmpdALength = bmpdA.length;
// loopN used to count number of enterframe loops and choose
// which bitmap to display. ie, which boyMovieClip frame.
loopN = 0;
}
Managing CPU/GPU Usage
function init():void {
var diagL:Number = Math.sqrt(boyW*boyW+boyH*boyH);
for (var i:int=num-1;i>=0; i–) {
datumA = [];
// Create the Bitmaps that will be added to the display list
datumA[0] = new Bitmap(null,"auto",true);
// Add to the display list
addChild(datumA[0]);
// Assign initial positions
datumA[0].x = diagL+int(Math.random()*(stageH-2*diagL));
datumA[0].y = diagL+int(Math.random()*(stageH-2*diagL));
datumA[0].alpha = Math.random();
// use speed and initialDirection to define vectorX and vectorY
speed = 5+10*Math.random();
initialDirection = -Math.PI/2+Math.PI*Math.random();
datumA[0].rotation = 180*initialDirection/Math.PI;
// store vectorX and vectorY
datumA[1] = speed*Math.cos(initialDirection);
datumA[2] = speed*Math.sin(initialDirection);
dataA[i] = datumA;
}
this.addEventListener(Event.ENTER_FRAME,animateBitmapsF);
}
function animateBitmapsF(e:Event):void {
//this.removeEventListener(Event.ENTER_FRAME,animateBitmapsF);
for(i=num-1;i>=0;i– –){
dataA[i][0].x += 1*dataA[i][1];
dataA[i][0].y += 1*dataA[i][2];
dataA[i][0].alpha = Math.random();
if (dataA[i][0].x>stageW-boyW || dataA[i][0].x<boyW) {
dataA[i][0].x -= 1*dataA[i][1];
dataA[i][1] *= -1;
dataA[i][0].rotation = 180-dataA[i][0].rotation;
}
if (dataA[i][0].y>stageH-boyW || dataA[i][0].y<boyW) {
dataA[i][0].y -= 1*dataA[i][2];
dataA[i][2]*=-1;
dataA[i][0].rotation *= -1;
}
dataA[i][0].bitmapData = bmpdA[loopN%bmpdALength];
}
loopN++;
}
255
256
Chapter 7 n Optimizing Game Performance
Type All Variables
Specify the class type of all variables. In addition to getting more compiler help
detecting errors, code with typed variables runs faster than code with untyped
variables.
For example, the following code:
var i:int;
var n:int = 10000000;
var sum:int = 0;
var startTime:int = getTimer();
for(i=0;i<n;i++){
sum+=i;
}
trace(getTimer()-startTime,sum,n*(n-1)/2);
runs about five times faster than:
var i:*;
var n:* = 10000000;
var sum:* = 0;
var startTime:* = getTimer();
for(i=0;i<n;i++){
sum+=i;
}
trace(getTimer()-startTime,sum,n*(n-1)/2);
Use Vectors Instead of Arrays
See the test files in the array_v_vector folder.
array_vector_arithmetic.fla
// small benefit of vectors over arrays confirmed.
var n:int = 1000000;
var i:int;
var startTime:int;
var sum:Number;
///*
// array test
startTime = getTimer();
var a:Array = new Array(n);
for(i=0;i<n;i++){
a[i] = int(Math.random()*n);
}
sum = 0;
Managing CPU/GPU Usage
for(i=0;i<n;i++){
sum+=a[i];
}
trace("array test duration:",getTimer()-startTime,"|| ave = ",int(sum/n),": ave
predicted = ",n/2);
// output: array test: 366 || ave = 500049 : ave predicted = 500000
//*/
///*
// vector test
startTime = getTimer();
var pool:Vector.<int> = new Vector.<int>(n);
for(i=0;i<n;i++){
pool[i] = int(Math.random()*n);
}
sum = 0
for(i=0;i<n;i++){
sum+=pool[i];
}
trace("vector test duration:",getTimer()-startTime,"|| ave = ",int(sum/n),": ave
predicted = ",n/2);
// output: vector test: 168 || ave = 499695 : ave predicted = 500000
//*/
array_v_vector_MovieClips.fla
// small benefit of vectors over arrays confirmed
import flash.display.MovieClip;
var mc:MovieClip
var n:int = 100000;
var i:int;
var startTime:int;
///*
// array test
startTime = getTimer();
var a:Array = new Array(n);
for(i=0;i<n;i++){
a[i] = new MovieClip();
}
for(i=0;i<n;i++){
mc = a.pop();
}
trace("array test duration:",getTimer()-startTime);
// output: array test: 750
//*/
257
258
Chapter 7 n Optimizing Game Performance
///*
// vector test
startTime = getTimer();
var pool:Vector.<MovieClip> = new Vector.<MovieClip>(n);
for(i=0;i<n;i++){
pool[i] = new MovieClip();
}
for(i=0;i<n;i++){
mc = pool.pop();
}
trace("vector test duration:",getTimer()-startTime);
// output: vector test: 590
//*/
Summary
Remember those five steps the Flash Player executes 24 times per second in a game
with 24 fps? Each of the optimization techniques reviewed in this chapter will help
minimize the time needed by the Flash Player to complete one or more of those
steps.
Chapter 8
Developing and Distributing
Games for iOS Devices
In this chapter we’ll examine the steps to develop, test, and deploy a game for iOS
(iPhone and iPad) devices. We’ll start with the tank combat game from Chapter 6,
“Developing a Flash Game,” and adapt it to work on an iPad.
After going through the code for the iPad tank combat game, we’ll cover some technical details: namely, how to test, publish, and distribute a game developed for an iOS
device. Those topics follow the iPad tank combat game code and are the most important parts of this chapter.
The Tank Combat Game for the iPad
The first thing we need to change is all the mouse event listeners, because tablets and
mobiles don’t have a mouse. That said, MouseEvent.MOUSE_DOWN events work well with
touch devices, but MouseEvent.MOUSE_UP and MouseEvent.CLICK events don’t. So, for
some games you can use MouseEvent.MOUSE_DOWN, and you won’t need to use any touch
events.
But for the tank combat game, we have sliders to drag and the player tank to control.
We’ll use touch events for the sliders and the accelerometer to control the player
tank.
Also, the display size of the non-retina iPad is 1024 × 768 pixels with 20 pixels for
the status bar, so we’ll change the stage size to 1024 × 748 and make changes to the
_introView, _combatView, and _gameOverView symbols. We’ll also make our own slider
because the slider component won’t work with touch events, and we’ll resize our button and check box to make them easier to use for those with less slender fingertips.
259
260
Chapter 8 n Developing and Distributing Games for iOS Devices
There is a possibility of major problems with memory management, and we want to
eliminate the need for gc’ing that might occur during combat, when a momentary
decrease in performance would be problematic. So we’ll use pool classes for IntroView,
CombatView, and GameOverView, as well as the player tanks and enemy tanks.
I’m also concerned about the performance hit from having each tank use its own
listeners, so we’ll use a Controller class to handle the movement,
aiming, and firing of all the tanks and a controller pool to create and furnish the
Controller class when it is needed.
Event.ENTER_FRAME
In addition, we’ll add code to prevent tanks from colliding, and we’ll remove the
slider to control rotation in case we need to use partial blitting to get decent performance. (As it turns out, we won’t need to use blitting, but let’s leave the slider out
anyway because it doesn’t add much to the game.)
We’ll also add a restriction on how frequently a ShotSound can play so we can avoid
the 32 sound-channel limit imposed by the Flash Player. That is a pretty generous
limit, but we could easily exceed it by increasing the number of enemy tanks and
the number of shells each tank can have on-screen at one time. This is especially
noticeable at the start of combat, when all the EnemyTank instances fire. That would
cause all sounds to stop working until enough sounds ended to clear the buffer.
The files are in /support files/Chapter 8/tank_combat_iOS. There are many changes
from version 9, starting with Main. I’ve commented the code to explain the changes
made in this iOS version.
The Main class initializes data and handles adding and removing all the views to the
display list.
Main
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.getTimer;
public class Main extends MovieClip {
private var introView:IntroView;
private var combatView:CombatView;
private var gameOverView:GameOverView;
public function Main() {
// MT.init(this,3);
// Initialize all variables to default values
Data.resetAllF();
// Initialize all the views and tanks
initializePoolsF();
The Tank Combat Game for the iPad
addIntroViewF();
}
private function initializePoolsF():void{
// Twenty EnemyTank instances are created in
// EnemyTank_Pool. The EnemyTank’s parent (arena_mc)
// width and height are hard-coded.
EnemyTank_Pool.init(20,980,620);
// Two PlayerTank instances are created in
// PlayerTank_Pool. One for introView and one for
// combatView. The first two parameters are the width
// and height of the PlayerTank’s introView parent
// (playerBG_mc), and the second two parameters are
// the width and height of PlayerTank’s combatView
// parent (arena_mc).
PlayerTank_Pool.init(354,460,980,620);
// Each of the views and Controller is created in
// its pool classes.
IntroView_Pool.init(1);
CombatView_Pool.init(1);
GameOverView_Pool.init(1);
Controller_Pool.init(1);
}
private function addIntroViewF():void{
// IntroView instance retrieved from IntroView_Pool.
// Similarly, for the other views. IntroView_Pool is
// below so you can see how IntroView_Pool.retrieveF()
// and IntroView_Pool.returnF() work.
introView = IntroView_Pool.retrieveF();
introView.addEventListener("startCombatE",addCombatViewF,false,0,true);
addChild(introView);
}
private function removeIntroViewF():void{
introView.removeEventListener("startCombatE",addCombatViewF,false);
removeChild(introView);
// Instead of readying introView for gc, it is returned
// to IntroView_Pool so it is ready to be reused if the
// game is replayed. Likewise, for the other views, as
// you can see in the code to retrieve and return
// combatView and gameOverView.
IntroView_Pool.returnF(introView);
}
private function addCombatViewF(e:Event):void{
261
262
Chapter 8 n Developing and Distributing Games for iOS Devices
removeIntroViewF();
combatView = CombatView_Pool.retrieveF();
combatView.addEventListener("endCombatE",addGameOverViewF,false,0,true);
addChild(combatView);
stage.focus = combatView;
}
private function removeCombatViewF():void{
combatView.removeEventListener("endCombatE",addGameOverViewF,false);
removeChild(combatView);
CombatView_Pool.returnF(combatView);
}
private function addGameOverViewF(e:Event):void{
removeCombatViewF();
gameOverView = GameOverView_Pool.retrieveF();
gameOverView.addEventListener("replayE",replayF,false,0,true);
addChild(gameOverView);
}
private function removeGameOverViewF():void{
gameOverView.removeEventListener("replayE",replayF,false);
removeChild(gameOverView);
GameOverView_Pool.returnF(gameOverView);
}
private function replayF(e:Event):void{
removeGameOverViewF();
addIntroViewF();
}
}
}
The
IntroView
class allows the player to customize the game.
IntroView
package com.kglad {
import flash.display.MovieClip;
import flash.events.TouchEvent;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.sensors.Accelerometer;
import flash.events.AccelerometerEvent;
The Tank Combat Game for the iPad
public class IntroView extends MovieClip {
// An array of all movieclips that contain a slider,
// defined in init(). Used to ease coding for the slider
// mouse listeners and for assigning the initial values
// of the sliders.
private var mc_sliderA:Array;
private var controlsA:Array;
private var sliderMinMaxA:Array;
private var controller:Controller;
private var accelerometer:Accelerometer;
private var accelerometerE:AccelerometerEvent;
var player:PlayerTank
public function IntroView() {
// Note: in IntroView, CombatView, and GameOverView,
// things that only need to execute once are in the
// constructor. Things that need to reset, when one
// of these classes is added to the display list, are
// in init(). I want to use the iPad’s accelerometer
// to control player movement.
accelerometer = new Accelerometer();
controlsA = [forward,back,left,right];
mc_sliderA =
[tankSpeed_mc,shellSpeed_mc,maxShells_mc,maxHits_mc,enemyNum_mc,
enemyEvasiveness_mc,startTime_mc,combatEndDelay_mc];
sliderMinMaxA =
[[1,10],[1,40],[1,10],[1,10],[1,20],[1,10],[0,10],[0,10]];
// There are two input modes. MultitouchInputMode.TOUCH_POINT
// (when you only need to detect one touch at a time) and the
// default MultitouchInputMode.GESTURE (when you need to
// detect more than one touch point at a time, like when
// zooming and rotating).
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,
true);
// Clean up everything in this class when no longer needed.
// That is, remove listeners and return PlayerTank and
// Controller instances to their pools.
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
// Add listener for accelerometer changes.
263
264
Chapter 8 n Developing and Distributing Games for iOS Devices
accelerometer.addEventListener(AccelerometerEvent.UPDATE,
accelerometerF,false,0,true);
listenersF();
addPlayerTankAndControllerF();
}
// add listeners and assign default values
private function listenersF():void{
for(var i:int=controlsA.length-1;i>=0;i– –){
// The TouchEvent class has several properties
// I can use. The TouchEvent.TOUCH_OVER is similar
// to the MouseEvent.MOUSE_OVER. There is also a
// TouchEvent.TOUCH_ROLL_OVER, which is similar to
// MouseEvent.ROLL_OVER, which dispatches repeat
// events if children of the currentTarget undergo
// the event.
controlsA[i].addEventListener(TouchEvent.TOUCH_OVER,explanationF,false,0,true);
// TouchEvent.TOUCH_TAP is analogous to the
// MouseEvent.CLICK event.
controlsA[i].addEventListener(TouchEvent.TOUCH_TAP,controlsF,false,0,true);
}
startCombat_mc.addEventListener(TouchEvent.TOUCH_TAP,startCombatF,false,0,true);
// Slider listeners
for(i=mc_sliderA.length-1;i>=0;i– –){
// Check the Slider class for min,max getters
// and setters
mc_sliderA[i].sl.min = sliderMinMaxA[i][0];
mc_sliderA[i].sl.max = sliderMinMaxA[i][1];
mc_sliderA[i].addEventListener(TouchEvent.TOUCH_OVER,explanationF,false,0,true);
mc_sliderA[i].sl.addEventListener(TouchEvent.TOUCH_MOVE,sliderChangeF,false,0,
true);
setValuesF(i);
}
// Coding for the free-for-all movieclip button.
freeForAll_mc.cbox.addEventListener(TouchEvent.TOUCH_TAP,freeForAllF,false,0,
true);
freeForAll_mc.addEventListener(TouchEvent.TOUCH_OVER,freeForAllExplanationF,
false,0,true);
setValuesF(-1);
// Coding for the reset button that resets parameters
// to default values.
The Tank Combat Game for the iPad
reset_mc.addEventListener(TouchEvent.TOUCH_TAP,resetF,false,0,true);
}
// Initial values for the sliders and free-for-all.
private function setValuesF(i:int):void{
if(i>-1){
mc_sliderA[i].sl.value =
Data[Data.defaultVariableA[i+5].split("_")[1]];
mc_sliderA[i].tf.text =
Data[Data.defaultVariableA[i+5].split("_")[1]];
} else {
// freeForAll_mc.cbox has two frames. frame 1
// without the check mark, frame 2 with the check mark.
if(Data[Data.defaultVariableA[0].split("_")[1]]){
freeForAll_mc.cbox.gotoAndStop(2);
} else {
freeForAll_mc.cbox.gotoAndStop(1);
}
}
}
// This is where the text for the IntroView TextField (tf)
// is assigned.
private function explanationF(e:TouchEvent):void{
var val:int = e.currentTarget.sl.value
switch(e.currentTarget.name){
case "tankSpeed_mc":
tf.text = "Controls tank speed from a minimum
of "+e.currentTarget.sl.min+" to a maximum of "+e.currentTarget.sl.max+"\nCurrent
tank speed is "+val;
break;
case "shellSpeed_mc":
tf.text = "Controls tank shell speed from a
minimum of "+e.currentTarget.sl.min+" to a maximum of "+e.currentTarget.sl.max+"
\nCurrent tank shell speed is "+val;
break;
case "maxShells_mc":
tf.text = "Controls maximum number of shells
each tank can have on-screen at any one time. You can adjust from a minimum of
"+e.currentTarget.sl.min+" to a maximum of "+e.currentTarget.sl.max+"\nCurrent tank
shell maximum number is "+val;
break;
case "maxHits_mc":
265
266
Chapter 8 n Developing and Distributing Games for iOS Devices
tf.text = "Controls number of shell hits each
tank can withstand before destruction. You can adjust from a minimum of
"+e.currentTarget.sl.min+" to a maximum of "+e.currentTarget.sl.max+"\nCurrent
number of shell hits before destruction is "+val;
break;
case "enemyNum_mc":
tf.text = "Controls the number of enemy
tanks. You can adjust from a minimum of "+e.currentTarget.sl.min+" to a maximum of
"+e.currentTarget.sl.max+"\nCurrent enemy number is "+val;
break;
case "enemyEvasiveness_mc":
tf.text = "Controls the frequency of enemy
tank evasive manuevers. You can adjust from a minimum of "+e.currentTarget.sl.min+" to
a maximum of "+e.currentTarget.sl.max+"\nCurrent enemy tank evasiveness is "+val;
break;
case "startTime_mc":
tf.text = "Controls the delay from the intial
combat screen until the first shot is allowed. You can adjust from a minimum of
"+e.currentTarget.sl.min+" second(s) to a maximum of "+e.currentTarget.sl.max+"
\nCurrent delay is "+val;
break;
case "combatEndDelay_mc":
tf.text = "Controls the delay from the end of
combat until the End Game Screen. You can adjust from a minimum of
"+e.currentTarget.sl.min+" second(s) to a maximum of "+e.currentTarget.sl.max+"
\nCurrent delay is "+val;
break;
}
}
// Data class setters called when there is a slider change.
private function sliderChangeF(e:Event):void{
var val:int = int(e.currentTarget.value);
switch(e.currentTarget.parent.name){
case "tankSpeed_mc":
tf.text = "Controls tank speed from a minimum
of "+e.currentTarget.min+" to a maximum of "+e.currentTarget.max+"\nCurrent tank
speed is "+val;
Data.tankSpeed = val;
e.currentTarget.parent.tf.text = val;
break;
case "shellSpeed_mc":
tf.text = "Controls tank shell speed from a
minimum of "+e.currentTarget.min+" to a maximum of "+e.currentTarget.max+"\nCurrent
tank shell speed is "+val;
The Tank Combat Game for the iPad
Data.shellSpeed = val;
e.currentTarget.parent.tf.text = val;
break;
case "maxShells_mc":
tf.text = "Controls maximum number of shells
each tank can have on-screen at any one time. You can adjust from a minimum of
"+e.currentTarget.min+" to a maximum of "+e.currentTarget.max+"\nCurrent tank shell
maximum number is "+val;
Data.maxShells = val;
e.currentTarget.parent.tf.text = val;
break;
case "maxHits_mc":
tf.text = "Controls number of shell hits each
tank can withstand before destruction. You can adjust from a minimum of
"+e.currentTarget.min+" to a maximum of "+e.currentTarget.max+"\nCurrent number of
shell hits before destruction is "+val;
Data.maxHits = val;
e.currentTarget.parent.tf.text = val;
break;
case "enemyNum_mc":
tf.text = "Controls the number of enemy
tanks. You can adjust from a minimum of "+e.currentTarget.min+" to a maximum of
"+e.currentTarget.max+"\nCurrent enemy number is "+val;
Data.enemyNum = val;
e.currentTarget.parent.tf.text = val;
break;
case "enemyEvasiveness_mc":
tf.text = "Controls the frequency of enemy
tank evasive manuevers. You can adjust from a minimum of "+e.currentTarget.min+" to a
maximum of "+e.currentTarget.max+"\nCurrent enemy tank evasiveness is "+val;
Data.enemyEvasiveness = val;
e.currentTarget.parent.tf.text = val;
break;
case "startTime_mc":
tf.text = "Controls the delay from the start
of the combat screen until the first shot is allowed. You can adjust from a minimum of
"+e.currentTarget.min+" second(s) to a maximum of "+e.currentTarget.max+"\nCurrent
delay is "+val;
Data.startTime = val;
e.currentTarget.parent.tf.text = val;
break;
case "combatEndDelay_mc":
tf.text = "Controls the delay from the end of
combat until the End Game Screen. You can adjust from a minimum of
267
268
Chapter 8 n Developing and Distributing Games for iOS Devices
"+e.currentTarget.min+" second(s) to a maximum of "+e.currentTarget.max+"\nCurrent
delay is "+val;
Data.combatEndDelay = val;
e.currentTarget.parent.tf.text = val;
break;
}
}
private function accelerometerF(e:AccelerometerEvent):void{
// AccelerometerEvent is saved for use in controlsF()
accelerometerE = e;
}
// The saved AccelerometerEvent’s accelerationX and
// accelerationY properties change when the iPad is
// pitched and rotated. The user can specify the
// limits of pitch and rotation that should be used
// to control player’s forward, back, left, and right
// movement. See controlsExplanationF() below.
private function controlsF(e:TouchEvent):void{
switch(e.currentTarget.name){
case "forward":
Data[e.currentTarget.name] =
accelerometerE.accelerationY;
break;
case "back":
Data[e.currentTarget.name] =
accelerometerE.accelerationY;
break;
case "left":
Data[e.currentTarget.name] =
accelerometerE.accelerationX;
break;
case "right":
Data[e.currentTarget.name] =
accelerometerE.accelerationX;
break;
}
}
private function controlsExplanationF(e:TouchEvent):void{
switch(e.currentTarget.name){
case "forward":
tf.text = "Angle the top of your device, then
click this button to assign the rotation to move your tank forward. Leave some dead
space between the forward and back rotations so you can also stop your tank.";
break;
The Tank Combat Game for the iPad
case "back":
tf.text = "Angle the top of your device, then
click this button to assign the rotation to move your tank backward. Leave some dead
space between the forward and back rotations so you can also stop your tank.";
break;
case "left":
tf.text = "Rotate your device like a steering
wheel to turn left and right. Rotate to the left and then click this button to assign the
rotation to turn left. Leave some dead space between the left and right rotations so you
can also go straight.";
break;
case "right":
tf.text = "Rotate your device like a steering
wheel to turn left and right. Rotate to the right and then click this button to assign
the rotation to turn right. Leave some dead space between the left and right rotations
so you can also go straight.";
break;
}
}
// freeForAll_mc.cbox clicked:
private function freeForAllF(e:TouchEvent):void{
var mc:MovieClip = MovieClip(e.currentTarget);
// If the check box’s frame 1 is displayed (unchecked),
// goto frame 2 (checked) and update _freeForAll in Data
// using the setter. Else, goto frame 1 and update
// _freeForAll in Data using the setter.
if(mc.currentFrame==1){
mc.gotoAndStop(2);
Data.freeForAll = true;
} else {
mc.gotoAndStop(1);
Data.freeForAll = false;
}
}
// freeForAll_mc explanation.
private function freeForAllExplanationF(e:TouchEvent):void{
tf.text = "Indicates whether combat is a free-for-all (all
tanks fight each other) or not (it’s you against all enemy tanks)";
}
// Reset all sliders and checkbox and parameters in Data
private function resetF(e:TouchEvent):void{
Data.resetAllF();
for(var i:intmc_sliderA.length-1;i>=-1;i–){
setValuesF(i);
269
270
Chapter 8 n Developing and Distributing Games for iOS Devices
}
}
private function addPlayerTankAndControllerF():void{
// PlayerTank_Pool has two non-interchangeable instances
// of PlayerTank. One for IntroView and one for CombatView,
// so the retrieve and return functions are different for
// each.
player = PlayerTank_Pool.retrieveIntroF();
positionF(playerBG_mc,player);
// Retrieve the only Controller instance.
controller = Controller_Pool.retrieveF();
// Initialize the controller by calling init() and passing
// the PlayerTank instance and the array of enemies, which
// is empty in IntroView.
controller.init(player,[]);
}
private function positionF(parent_mc:MovieClip,mc:MovieClip):void {
parent_mc.addChild(mc);
mc.x = int(parent_mc.width/2);
mc.y = int(parent_mc.height/2);
}
private function startCombatF(e:TouchEvent):void{
dispatchEvent(new Event("startCombatE"));
}
private function cleanupF(e:Event):void{
// Remove all the listeners created here and return pool
// instances
removeListenersF();
PlayerTank_Pool.returnIntroF(player);
controller.dispatchEvent(new Event("cleanupE"));
Controller_Pool.returnF(controller);
}
private function removeListenersF():void{
accelerometer.removeEventListener(AccelerometerEvent.
UPDATE, accelerometerF,false);
for(var i:int=controlsA.length-1;i>=0;i– –){
controlsA[i].removeEventListener(TouchEvent.TOUCH_OVER,explanationF,false);
controlsA[i].removeEventListener(TouchEvent.TOUCH_TAP,controlsF,false);
}
startCombat_mc.removeEventListener(TouchEvent.TOUCH_TAP,startCombatF,false);
The Tank Combat Game for the iPad
for(i=mc_sliderA.length-1;i>=0;i– –){
mc_sliderA[i].removeEventListener(TouchEvent.TOUCH_OVER,explanationF,false);
mc_sliderA[i].sl.removeEventListener(TouchEvent.TOUCH_MOVE,sliderChangeF,false);
}
freeForAll_mc.cbox.removeEventListener(TouchEvent.TOUCH_TAP,freeForAllF,false);
freeForAll_mc.removeEventListener(TouchEvent.TOUCH_OVER,freeForAllExplanationF,
false);
reset_mc.removeEventListener(TouchEvent.TOUCH_TAP,resetF,
false);
}
}
}
The
IntroView_Pool
class supplies the IntroView instance.
IntroView_Pool
package com.kglad{
public class IntroView_Pool {
private static var pool:Vector.<IntroView>;
public static function init(poolSize:int):void {
// The next line creates a vector with length = poolSize
// and populates the vector with IntroView instances
// (that are null). That means if you use
// pool.push(new IntroView()), pool.length =
// 2*poolSize and half of pool’s elements will
// be null.
pool = new Vector.<IntroView>(poolSize);
for(var i:int=poolSize-1;i>=0;i++){
pool[i] = new IntroView();
}
}
public static function retrieveF():IntroView {
if (pool.length>0) {
return pool.pop();
} else {
// This branch should not execute.
return new IntroView();
}
}
public static function returnF(view:IntroView):void {
pool.push(view);
271
272
Chapter 8 n Developing and Distributing Games for iOS Devices
}
}
}
The Slider class is needed because the Flash Slider component didn’t work on the
iPad.
Slider
package com.kglad {
import
import
import
import
import
import
flash.display.MovieClip;
flash.ui.Multitouch;
flash.ui.MultitouchInputMode;
flash.events.TouchEvent;
flash.events.Event;
flash.geom.Rectangle;
public class Slider extends MovieClip {
private var leftX;
private var rightX;
private var _value:Number;
private var _max:Number;
private var _min:Number;
public function Slider() {
// This is a horizontal slider so define the left and
// right extremes of the thumb (which has a center x
// registration point). When the slider
// thumb.x = leftX, _value should be _min. When
// the slider thumb.x = rightX, _value should be _max.
// Linear interpolation is used to determine _value when
// thumb.x is between leftX and rightX.
leftX = this.track_mc.getRect(this).x;
rightX = this.track_mc.getRect(this).width;
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Two other touch events defined exactly like you
// would expect
this.thumb.addEventListener(TouchEvent.TOUCH_BEGIN,
touchBeginF);
this.thumb.addEventListener(TouchEvent.TOUCH_MOVE,
touchMoveF);
The Tank Combat Game for the iPad
this.thumb.addEventListener(TouchEvent.TOUCH_END,
touchEndF);
}
private function touchBeginF(e:TouchEvent):void{
// TouchEvents have a touchPointID, which is necessary
// to keep track of different simultaneous touch points
// when Multitouch.inputMode = MultitouchInputMode.GESTURE.
// With single touch points like in this game, keeping
// track of touch points is not critical, but it is
// required for the startTouchDrag() and stopTouchDrag()
// methods. The other parameters in startTouchDrag() are
// the same as startDrag().
e.currentTarget.startTouchDrag(e.touchPointID, false, new
Rectangle(0,0,this.rightX,0));
}
private function touchMoveF(e:TouchEvent):void{
// Update _value
valueF();
}
private function touchEndF(e:TouchEvent):void{
valueF();
e.currentTarget.stopTouchDrag(e.touchPointID);
}
private function valueF():void{
// Linear interpolation to find _value given thumb.x
_value = (_min*rightX-_max*leftX+(_max_min)*
this.thumb.x)/(rightX-leftX);
}
public function get value():Number{
return _value;
}
public function set value(n:Number):void{
_value = n;
// Another use of linear interpolation to find
// thumb.x given _value
this.thumb.x = (_value*(rightX-leftX)-_min*rightX_max*leftX)/
(_max-_min);
}
public function set max(n:Number):void{
_max = n;
}
public function get max():Number{
return _max;
}
273
274
Chapter 8 n Developing and Distributing Games for iOS Devices
public function set min(n:Number):void{
_min = n;
}
public function get min():Number{
return _min;
}
}
}
The
CombatView
class handles the actual tank combat.
CombatView
package com.kglad {
import flash.display.MovieClip;
import flash.display.DisplayObject;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.utils.getTimer;
public class CombatView extends MovieClip {
// Variables that were initialized here have been moved to
// init() so they can be reinitialized each time this is added
// to the display list.
private var player:PlayerTank;
private var enemyNum:int;
private var enemyA:Array = [];
private var maxHits:int;
private var timeTilGameOverView;
private var startTime:int;
// This is a new variable for the Controller class.
private var controller:Controller;
private var i:int;
public function CombatView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,
true);
this.addEventListener(Event.REMOVED_FROM_STAGE,removedF,false,0,true);
}
private function init(e:Event):void{
// Initialize variables
enemyNum = Data.enemyNum;
maxHits = Data.maxHits;
startTime = Data.startTime;
timeTilGameOverView = 1000*Data.combatEndDelay;
The Tank Combat Game for the iPad
// Initialize the stats display.
statsDisplayF();
// Add player tank and enemy tank(s)
addTanksF();
}
private function statsDisplayF():void{
// Initialize the display of player score and player life
score_tf.text = "0";
life_tf.text = "100%";
}
private function addTanksF():void{
player = PlayerTank_Pool.retrieveCombatF();
// Add listeners for events dispatched from Tank to
// remove player from the arrays when destroyed,
// register a hit to player and when player "scores"
// or hits another tank
player.addEventListener("playerHitE",playerHitF,false,0,
true);
player.addEventListener("scoreE",scoreF,false,0,true);
// Retrieve maxHits from Data
maxHits = Data.maxHits;
player.name = "player";
positionF(arena_mc,player);
player.prevX = player.x;
player.prevY = player.y;
// Add enemy tank(s)
for(i=enemyNum-1;i>=0;i– –){
var enemy:EnemyTank = EnemyTank_Pool.retrieveF();
enemy.name = i.toString();
enemy.foeA.push(player);
positionF(arena_mc,enemy);
enemy.prevX = enemy.x;
enemy.prevY = enemy.y;
enemyA.push(enemy);
}
// all enemies are player foes.
player.foeA = player.foeA.concat(enemyA);
// player has no friends. So, I did not define a
// player.friendA. You’re on your own.
// There is a Data.freeForAll parameter chosen by
// the user to indicate a free-for-all
if(Data.freeForAll){
for(i=enemyNum-1;i>=0;i– –){
// Add all enemies to each enemy’s foeA
275
276
Chapter 8 n Developing and Distributing Games for iOS Devices
enemyA[i].foeA =
enemyA[i].foeA.concat(enemyA);
// Remove enemyA[i] so no one is
// their own foe.
enemyA[i].foeA.splice(i+1,1);
}
} else {
for(i=enemyNum-1;i>=0;i– –){
// add all enemies to each enemy’s friendA
enemyA[i].friendA = enemyA[i].friendA.
concat(enemyA);
// remove enemyA[i] so no one is their own
// friend. At this point, no one targets
// friends, but if a shell hits a friend,
// it counts. Later I may add code to check
// that no shot is apt to hit a friend.
enemyA[i].friendA.splice(i,1);
}
}
// All tanks created. Start countdown to combat:
startTimeF(startTime);
// all modifications to enemyA done in Controller
// affect enemyA here and vice versa.
//controller = new Controller(player,enemyA,test_tf);
controller = Controller_Pool.retrieveF();
controller.init(player,enemyA);
}
// I moved tanks further from the boundary.
private function positionF(arena_mc:MovieClip,mc:MovieClip):void {
arena_mc.addChild(mc);
mc.x = int(2*mc.width+(arena_mc.width-4*mc.width)*
Math.random());
mc.y = int(2*mc.height+(arena_mc.height-4*mc.height)*
Math.random());
// If this is an enemy tank, make sure it’s not
// hitting another tank. player is added first,
// so no need to check for a hit when it’s added.
//trace(mc.name);
if(mc!=player){
if(player.hitTestObject(mc)){
positionF(arena_mc,mc);
} else {
// check mc doesn’t hit any enemyA element
for(var i:int=enemyA.length-1;i>=0;i– –){
The Tank Combat Game for the iPad
if(mc.hitTestObject(enemyA[i])){
positionF(arena_mc,mc);
break;
}
}
}
}
}
// scoreF called via player scoreE event listener when
// player scores a hit on an enemy tank
private function scoreF(e:Event=null):void{
// Update stats display
score_tf.text = Data.score.toString();
if(enemyA.length == 0){
// If there are no more enemies, the game
// is over and player won. This function is
// where I delay removing this view and
// adding game over view.
delayLeavingCombatViewF();
}
}
// playerHitF called via player playerHitE event listener
// and possibly from removeTankF()
private function playerHitF(e:Event = null):void{
// This is where maxHits is used to update the
// stats display
life_tf.text = int(100*(maxHits-Data.playerHits)/maxHits)+"
%";
if(Data.playerHits==maxHits){
// Game over. you lost.
// Same delay function executes whether player
// wins or loses.
delayLeavingCombatViewF();
}
}
/////////////////////
// start combat delay
private function startTimeF(n:int):void{
// Display time until start
startTime_tf.text = n.toString();
// Create timer to count down the start time
// if startTime>0.
277
278
Chapter 8 n Developing and Distributing Games for iOS Devices
if(n>0){
var startTimer:Timer = new Timer(1000,n);
startTimer.addEventListener(TimerEvent.TIMER,timerF,false,0,true);
startTimer.start();
}
}
private function timerF(e:TimerEvent):void{
// Update start time countdown
startTime_tf.text = (e.target.repeatCount-e.target.current
Count).toString();
if(e.target.currentCount==e.target.repeatCount){
// Remove the listener when no longer needed.
e.target.removeEventListener(TimerEvent.TIMER,timerF,false);
}
}
// end combat delay
private function delayLeavingCombatViewF():void{
// This fixes that problem where player could be
// destroyed, but in free-for-all mode, enemy tanks
// could keep destroying each other, leading to
// possibly erroneous results being displayed in
// the GameOverView instance. Data.enemiesRemaining
// used in GameOverView accurately indicating the
// number of enemies left when player destroyed.
Data.enemiesRemaining = enemyA.length;
var delayTimer:Timer = new Timer(timeTilGameOverView,1);
delayTimer.addEventListener(TimerEvent.TIMER,endCombatF,false,0,true);
delayTimer.start();
}
/////////////////////
// Finally, dispatch the endCombatE event so Main can
// remove the CombatView instance and add a GameOverView
// instance.
private function endCombatF(e:Event):void{
e.target.removeEventListener(TimerEvent.TIMER,endCombatF,
false);
dispatchEvent(new Event("endCombatE"));
}
private function removedF(e:Event):void{
// Do not remove this event listener
//this.removeEventListener(Event.REMOVED_FROM_STAGE,removedF,false);
if(player.hasEventListener("playerHitE")){
The Tank Combat Game for the iPad
player.removeEventListener("playerHitE",playerHitF,false);
}
// Remove this event listener
if(player.hasEventListener("scoreE")){
player.removeEventListener("scoreE",scoreF,false);
}
for(var i:int=arena_mc.numChildren-1;i>=0;i– –){
var c:DisplayObject = arena_mc.getChildAt(i);
if(c is MovieClip){
arena_mc.removeChildAt(i);
}
}
// empty enemyA
for(i=enemyA.length-1;i>=0;i– –){
enemyA[i].friendA.length=0;
enemyA[i].foeA.length=0;
EnemyTank_Pool.returnF(enemyA[i]);
}
enemyA.length = 0;
if(player){
if(player.foeA){
player.foeA.length = 0;
}
PlayerTank_Pool.returnCombatF(player);
}
controller.dispatchEvent(new Event("cleanupE"));
Controller_Pool.returnF(controller);
}
}
}
The
CombatView_Pool supplies the CombatView instance (used
IntroView to show how the player’s tank moves and shoots
for combat and in
while setting up the
controls).
CombatView_Pool
package com.kglad{
public class CombatView_Pool {
private static var pool:Vector.<CombatView>;
public static function init(poolSize:int):void {
pool = new Vector.<CombatView>(poolSize);
for(var i:int=poolSize-1;i>=0;i– –){
pool[i] = new CombatView();
279
280
Chapter 8 n Developing and Distributing Games for iOS Devices
}
}
public static function retrieveF():CombatView {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new CombatView();
}
}
public static function returnF(view:CombatView):void {
pool.push(view);
}
}
}
The
Controller
class handles user input and player tank actions.
Controller
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Stage;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.utils.getTimer;
import flash.sensors.Accelerometer;
import flash.events.AccelerometerEvent;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.TouchEvent;
public class Controller extends MovieClip{
private var player:PlayerTank;
private var directionA:Array = [];
private var enemyA:Array;
private var tankA:Array;
private var _stage:Stage;
private var maxShells:int;
private var maxHits:int;
private var shellHit:Boolean;
private var startTime:int;
private var initTime:int;
The Tank Combat Game for the iPad
protected const d2r:Number = Math.PI/180;
protected var speed:int;
protected var rotationRate:int;
private var shellSpeed:int;
private var shotSound:Sound = new ShotSound();
// I added this variable to prevent too many shot sounds
// from occurring too close together in time. I noticed
// there was a problem caused by a Flash maximum number
// of concurrent sounds.
private var lastShotTime:int;
private var destroySound:Sound = new DestroySound();
private var keyCodeIndex:int;
private var xLimit:int;
private var yLimit:int;
private var newDirectionFreq:int;
private var newDirectionTimer:Timer;
private var accelerometer:Accelerometer;
// bv - boundary violation. tc - tank collision. used in
// enemyMoveLimitsF
private var bv:String;
private var tc:String;
private var enemyA_len:int;
private var tankA_len:int;
public function Controller() {
accelerometer = new Accelerometer();
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener("cleanupE",cleanupF,false,0,true);
}
public function init(_player:PlayerTank,_enemyA:Array):void{
// Controller is not added to the stage. So, I use
// an init() method that is called from IntroView
// and CombatView when those two class instances are
// added to the stage.
lastShotTime = 0;
speed = Data.tankSpeed;
shellSpeed = Data.shellSpeed;
maxShells = Data.maxShells;
maxHits = Data.maxHits;
startTime = Data.startTime;
speed = Data.tankSpeed;
rotationRate = Data.rotationRate;
newDirectionFreq = 10000/Data.enemyEvasiveness;
accelerometer.addEventListener(AccelerometerEvent.UPDATE,
controlChangePlayerF,false,0,true);
281
282
Chapter 8 n Developing and Distributing Games for iOS Devices
player = _player;
enemyA = _enemyA;
enemyA_len = enemyA.length;
xLimit = player.xLimit;
yLimit = player.yLimit;
tankA = enemyA.concat(player);
tankA_len = tankA.length;
_stage = player.stage;
allTanksF();
enemyTankF();
playerTankF();
}
private function allTanksF():void{
// initTime is used to ensure no tank starts firing
// for first few seconds
initTime = getTimer()+startTime*1000;
}
private function enemyTankF():void{
newDirectionF();
this.addEventListener(Event.ENTER_FRAME,directionChangeF,false,0,true);
this.addEventListener(Event.ENTER_FRAME,rotateEnemyTurretF,false,0,true);
this.addEventListener(Event.ENTER_FRAME,moveEnemyTankF,false,0,true);
newDirectionTimer = new Timer(newDirectionFreq,0);
newDirectionTimer.addEventListener(TimerEvent.TIMER,newDirectionF,false,0,true);
// define for each enemy and offset start() if unsync’d
// turning desired.
newDirectionTimer.start();
for(var i:int=enemyA_len-1;i>=0;i– –){
// Each EnemyTank instance needs a different
// Timer instance (moveBackTimer). When each
// moveBackTimer calls its listener function,
// I want to reference moveBackTimer’s
// associated EnemyTank. But I have no way to
// do that using the standard Timer instance.
// So, I created a new class that extends and
// adds one feature to the Timer class. I added
// a scope property that I can set and get.
enemyA[i].moveBackTimer = new TimerExt(1000,1);
enemyA[i].moveBackTimer.addEventListener(TimerEvent.TIMER,moveBackF,false,0,true);
The Tank Combat Game for the iPad
enemyA[i].moveBackTimer.scope = enemyA[i];
}
}
private function playerTankF():void{
player.score = 0;
this.addEventListener(Event.ENTER_FRAME,movePlayerTankF,false,0,true);
this.addEventListener(Event.ENTER_FRAME,rotatePlayerTurretF,false,0,true);
// TouchEvent.TOUCH_TAP I found to be unreliable
// whereas TouchEvent.TOUCH_BEGIN and
// TouchEvent.TOUCH_END are reliable.
// So, I added TouchEvent.TOUCH_END to arena_mc
// to trigger player to shoot. And I decided to
// allow the CustomCursor instance to be dragged
// and dropped.
player.parent.addEventListener(TouchEvent.TOUCH_BEGIN,
startDragF,false,0,true);
player.parent.addEventListener(TouchEvent.TOUCH_END, stopDragF,false,0,true);
}
/////////// begin all tanks /////////////////////
// tankCollisionF() replaces tank_v_tankCollisionsF and
// is called from enemyMoveLimitsF()right after
// boundaryViolationF(). Instead of destroying both tanks,
// I handle tank collisions like boundary violations.
// The tanks move back and then change direction.
// For the most part this works well.
private function tankCollisionF(tank:Tank):String{
for(var i:int=tankA_len-1;i>=1;i– –){
if(tank!=tankA[i] && tank.hitTestObject(tankA[i]) )
{
return quadrantF(Tank(tank));
}
}
return "";
}
// I use the same shootF for all EnemyTank instances and player
protected function shootF(tank:Tank):void{
if(tank.shellA.length<maxShells && tank.foeA.length>0 &&
getTimer()>initTime){
// I added this to avoid exceeding the Flash
// Player sound channel limit. There are other
// ways to do this, but I liked this best because
283
284
Chapter 8 n Developing and Distributing Games for iOS Devices
// there is no perceptible downside.
if(getTimer()-lastShotTime>500){
shotSound.play();
lastShotTime = getTimer();
}
var shell:Shell = new Shell();
tank.shellA.push(shell);
if(tank.parent){
tank.parent.addChild(shell);
}
// I changed this to decrease the number of
// calculations needed to update each shell’s
// position.
shell.vectorX =
shellSpeed*Math.cos((tank.turret_mc.rotation+tank.rotation)*d2r);
shell.vectorY =
shellSpeed*Math.sin((tank.turret_mc.rotation+tank.rotation)*d2r);
// shell’s initial position
shell.tankX = tank.x;
shell.tankY = tank.y;
shell.x = tank.x+tank.gunL*shell.vectorX/
shellSpeed;
shell.y = tank.y+tank.gunL*shell.vectorY/
shellSpeed;
}
// For EnemyTank instances, shootF is called in an
// enterFrame loop so shellLoopF is called repeatedly
// and updates the shell position of each EnemyTank.
if((tank is EnemyTank) && tank.shellA.length>0){
shellLoopF(tank);
} else if(player.shellA.length==1){
// For player, I need to add an enterFrame loop.
this.addEventListener(Event.ENTER_FRAME,shellLoopF,false,0,true);
}
}
private function shellLoopF(tankOrEvent):void{
// The passed parameter is either an EnemyTank instance
// or an Event.ENTER_FRAME event.
if(tankOrEvent is EnemyTank){
var tank:Tank = EnemyTank(tankOrEvent);
} else {
tank = player;
The Tank Combat Game for the iPad
}
var shellA:Array = tank.shellA;
for(var i:int=shellA.length-1;i>=0;i– –){
shellHit = false;
// The simplified calculation to determine each
// shell’s position using the parameters defined
// in shootF()
shellA[i].x += shellA[i].vectorX;
shellA[i].y += shellA[i].vectorY;
// Check for hit vs foes
hitF(tank.foeA,tank.shellA,i);
// If a foe hit and this is player, display stat
if(shellHit && tank.name == "player"){
PlayerTank(tank).score++;
Data.score = PlayerTank(tank).score;
// A scoreE event is dispatched to the
// PlayerTank instance, which, in
// CombatView, has a listener that
// updates the display
tank.dispatchEvent(new Event("scoreE"));
}
if(!shellHit){
// Check for hit vs friends
hitF(tank.friendA,tank.shellA,i);
}
// If shell not removed because of a hit,
// check for boundary violation.
if(!shellHit){
if(boundaryViolationF(shellA[i])){
shellRemoveF(shellA,i);
}
}
}
}
// friend/foe Array
private function hitF(ffA:Array,shellA:Array,shellIndex:int):void{
for(var j:int=ffA.length-1;j>=0;j– –){
// Check for shell hit
if(ffA[j].hitTestObject(shellA[shellIndex])){
// If a hit, assign shellHit
shellHit = true;
// Remove the shell
shellRemoveF(shellA,shellIndex);
// Update ffA[j]’s hits
285
286
Chapter 8 n Developing and Distributing Games for iOS Devices
ffA[j].hits++;
// If player hit, dispatch event so display
// can be updated.
if(ffA[j].name == "player"){
Data.playerHits = ffA[j].hits;
// Dispatch a playerHitE event to
// update the CombatView display.
ffA[j].dispatchEvent(new Event("playerHitE"));
}
// Check if ffA[j] has reached maxHits limit
if(ffA[j].hits>=maxHits){
// ffA[j] has been destroyed
destroyF(ffA[j]);
}
break;
}
}
}
private function destroyF(tank:Tank):void{
destroySound.play();
// Add explosion.
var explosion:Explosion = new Explosion();
// Listen for last frame to play in explosion (where a
// "removeE" event is dispatched).
explosion.addEventListener("removeE",removeExplosionF,false,0,true);
tank.parent.addChild(explosion);
// Position explosion where tank was located
explosion.x = tank.x;
explosion.y = tank.y;
removeTankF(tank);
}
private function shellRemoveF(shellA:Array,i:int):void{
// I saw null object reference error here once during
// testing, so I added this if-statement to prevent that.
if(shellA[i].parent){
shellA[i].parent.removeChild(shellA[i]);
}
shellA.splice(i,1);
if(player.shellA.length==0){
// Remove event listener if no longer needed. If
// already removed, not a problem.
The Tank Combat Game for the iPad
this.removeEventListener(Event.ENTER_FRAME,shellLoopF);
}
}
private function moveLimitsF(tank:Tank):void{
if(boundaryViolationF(tank)){
tank.x = tank.prevX;
tank.y = tank.prevY;
}
}
protected function boundaryViolationF(mc:MovieClip):String{
var b:Boolean = false;
if(mc.x<mc.width/2){
b=true;
} else if(mc.x>xLimit-mc.width/2){
b=true;
} else if(mc.y<mc.height/2){
b=true;
} else if(mc.y>yLimit-mc.height/2){
b=true;
}
if(b){
if(mc is Tank){
return quadrantF(Tank(mc));
} else {
return "true";
}
} else {
return "";
}
}
protected function quadrantF(tank:Tank):String{
if(!tank){
return "";
} else if(!tank.parent){
return "";
}
var quadrant:String;
if(tank.x<tank.parent.width/2){
quadrant="L";
} else {
quadrant="R";
}
if(tank.y<tank.parent.height/2){
287
288
Chapter 8 n Developing and Distributing Games for iOS Devices
quadrant+="U";
} else {
quadrant+="D";
}
return quadrant;
}
private function removeExplosionF(e:Event):void{
e.currentTarget.removeEventListener("removeE",removeExplosionF,false);
if(e.currentTarget.stage){
e.currentTarget.parent.removeChild(e.currentTarget);
}
}
// Destroyed tank (from Tank) is passed here so it can be
// removed from all arrays: enemyA, player.foeA, all
// enemyA member.foeA, and all enemyA.friendA
private function removeTankF(tank:Tank):void{
// Remove if a player foe.
if(player && tank.name!="player" && player.foeA){
for(var j2:int=player.foeA.length-1;j2>=0;j2– –){
if(player.foeA[j2].name==tank.name){
player.foeA.splice(j2,1);
break;
}
}
}
// Remove from enemyA before check enemyA element foeA
// and friendA. No need to look for tank in its own
// foeA or enemyA.
for(j2=enemyA.length-1;j2>=0;j2– –){
if(enemyA[j2].name==tank.name){
enemyA.splice(j2,1);
break;
}
}
for(j2=tankA.length-1;j2>=0;j2– –){
if(tankA[j2].name==tank.name){
tankA.splice(j2,1);
break;
}
}
for(j2=enemyA.length-1;j2>=0;j2– –){
// Remove if an enemy foe
The Tank Combat Game for the iPad
for(var j3:int=enemyA[j2].foeA.length-1;j3>=0;j3–){
if(enemyA[j2].foeA[j3].name==tank.name){
enemyA[j2].foeA.splice(j3,1);
break;
}
}
// Remove if an enemy friend
for(j3=enemyA[j2].friendA.length-1;j3>=0;j3– –){
if(enemyA[j2].friendA[j3].name==tank.name){
enemyA[j2].friendA.splice(j3,1);
break;
}
}
}
if(tank.name=="player"){
player.parent.removeEventListener(TouchEvent.
TOUCH_BEGIN, startDragF,false);
player.parent.removeEventListener(TouchEvent.
TOUCH_END, stopDragF,false);
// PlayerTank instance is returned to PlayerTank_Pool
// in CombatView
} else {
tank.friendA.length = 0;
tank.foeA.length = 0;
EnemyTank(tank).moveBackTimer.removeEventListener(TimerEvent.TIMER,
moveBackF,false);
EnemyTank_Pool.returnF(EnemyTank(tank));
}
tank.parent.removeChild(tank);
// Update array length variables.
tankA_len = tankA.length;
enemyA_len = enemyA.length;
}
/////////// end all tanks ///////////////////////
/////////// begin player tank //////////////////
private function startDragF(e:TouchEvent):void{
if(player.cCursor){
player.cCursor.x = e.localX;
player.cCursor.y = e.localY;
289
290
Chapter 8 n Developing and Distributing Games for iOS Devices
// Again, the startTouchDrag and stopTouchDrag
// methods need the touchPointID parameter.
player.cCursor.startTouchDrag(e.touchPointID,
false);
}
}
private function stopDragF(e:TouchEvent):void{
if(player.cCursor){
player.cCursor.stopTouchDrag(e.touchPointID);
// Here is where shootF is called by player when
// arena_mc’s TouchEvent.TOUCH_END event is
// dispatched.
shootF(player);
}
}
private function rotatePlayerTurretF(e:Event):void{
// if-statement used to prevent null object error.
if(player.cCursor && player.parent){
player.turret_mc.rotation = -player.rotation+Math.
atan2(player.cCursor.y-player.y,player.cCursor.x-player.x)/d2r;
}
}
// This is the code to control player’s movement based on
// the AccelerometerEvent dispatched by accelerometer.
private function controlChangePlayerF(e:AccelerometerEvent):void{
// e.accelerationX, e.accelerationY, and e.accelerationZ
// will change value as the iPad is rotated, pitched,
// and yawed where I use those terms as if the iPad’s
// screen were an airplane facing the user. In the
// shorthand below, I use rotationX for the pitch of
// the iPad, rotationY would be yaw but I don’t use it,
// and rotationZ is the iPad’s rotation. If you have
// trouble visualizing all that, create a test fla (if
// you have Flash CS6) and test. If you don’t have Flash
// CS6, you will need to use a textfield to see the
// results of your iPad manipulations.
// accelerationY: 1 -> 0 as rotationX: 0 -> -90
// It occurred to me that some people might want to go
// forward when the iPad’s rotationX decreases (and the
// pitch increases), whereas some might want to go
// forward with the opposite rotation.
// So, I have two if-branches for forward/back movement.
The Tank Combat Game for the iPad
// One where Data.forward<Data.back and the other where
// Data.forward>=Data.back.
if(Data.forward<Data.back){
if(e.accelerationY<Data.forward){
directionA[0] = true;
directionA[1] = false;
} else if(e.accelerationY>Data.back){
directionA[0] = false;
directionA[1] = true;
} else {
directionA[0] = false;
directionA[1] = false
}
} else {
if(e.accelerationY>Data.forward){
directionA[0] = true;
directionA[1] = false;
} else if(e.accelerationY<Data.back){
directionA[0] = false;
directionA[1] = true;
} else {
directionA[0] = false;
directionA[1] = false
}
}
// accelerationX: 1 -> 0 -> -1 as rotationZ:
// -90 -> 0 -> 90
// Data.right is negative, Data.left is positive
// I didn’t think anyone would want to turn right
// when they rotate left, so I didn’t add two
// branches for left/right movement.
if(e.accelerationX<Data.right){
directionA[2] = false;
directionA[3] =true;
} else if(e.accelerationX>Data.left){
directionA[2] = true;
directionA[3] = false;
} else {
directionA[2] = false;
directionA[3] = false;
}
}
private function movePlayerTankF(e:Event):void{
if(directionA[0]){
291
292
Chapter 8 n Developing and Distributing Games for iOS Devices
player.x += speed*Math.cos(player.rotation*d2r);
player.y += speed*Math.sin(player.rotation*d2r);
} else if(directionA[1]){
player.x -= speed*Math.cos(player.rotation*d2r);
player.y -= speed*Math.sin(player.rotation*d2r);
}
if(directionA[2]){
player.rotation -= rotationRate;
} else if(directionA[3]){
player.rotation += rotationRate;
}
moveLimitsF(player);
player.prevX = player.x;
player.prevY = player.y;
}
//////////////////// end player tank /////////////////////
/////////////////// begin enemy tank /////////////////////
private function newDirectionF(e:TimerEvent=null):void{
for(var i:int=enemyA_len-1;i>=0;i– –){
var tank:EnemyTank = EnemyTank(enemyA[i]);
if(!tank.moveBackBool){
// ai: turn towards stage center.
var quadrant:String = quadrantF(tank);
turnF(tank,quadrant);
}
}
}
private function rotateEnemyTurretF(e:Event):void{
for(var i:int=enemyA_len-1;i>=0;i– –){
var tank:EnemyTank = EnemyTank(enemyA[i]);
// Shoot at closest foe. This is for
// free-for-all mode.
var closestFoe:Tank = closestFoeF(tank);
// If there’s a foe, there’s a closest foe but
// there may not be any foes remaining.
if(closestFoe){
// Update turret rotation, then (try and)
// shoot.
tank.turret_mc.rotation = -tank.rotation+
Math.atan2(closestFoe.y-tank.y,closestFoe.x-tank.x)/d2r;
shootF(tank);
} else {
for(var j:int=tank.shellA.length-1;j>=0;j– –){
shellRemoveF(tank.shellA,j);
The Tank Combat Game for the iPad
}
}
}
}
private function closestFoeF(tank:EnemyTank):Tank{
var closestDist:Number = _stage.stageWidth*
_stage.stageWidth+_stage.stageHeight*_stage.stageHeight;
var closestFoe:Tank;
for(var i:int=tank.foeA.length-1;i>=0;i– –){
var dist:Number = distF(tank,tank.foeA[i]);
if(dist<closestDist){
closestDist = dist;
closestFoe = tank.foeA[i];
}
}
return closestFoe;
}
private function distF(t:Tank,foeT:Tank):Number{
return (t.x-foeT.x)*(t.x-foeT.x)+(t.y-foeT.y)*(t.y-foeT.y);
}
private function moveEnemyTankF(e:Event):void{
for(var i:int=enemyA_len-1;i>=0;i– –){
var tank:EnemyTank = EnemyTank(enemyA[i]);
if(tank.stage){
if(tank.startX==0){
tank.startX = this.x;
tank.startY = this.y;
}
if(!tank.moveBackBool){
tank.rotation = tank.currentDirection;
}
tank.x =
tank.startX+tank.distance*Math.cos(tank.currentDirection*d2r);
tank.y =
tank.startY+tank.distance*Math.sin(tank.currentDirection*d2r);
tank.distance += speed;
enemyMoveLimitsF(tank);
tank.prevX = tank.x;
tank.prevY = tank.y;
}
}
293
294
Chapter 8 n Developing and Distributing Games for iOS Devices
}
private function enemyMoveLimitsF(tank:EnemyTank):void{
bv = boundaryViolationF(tank);
tc = tankCollisionF(tank);
// If there is a boundary violation or tank v tank
// collision, move backwards and assign nextDirection
// to be towards stage-center.
if(bv || tc){
tank.x = tank.prevX;
tank.y = tank.prevY;
// Move in opposite direction
directionChangeUpdateF(tank);
tank.currentDirection = (180+tank.currentDirection)
%360;
tank.moveBackBool = true;
tank.moveBackTimer.start();
// assign nextDirection
if(bv){
turnF(tank,bv);
} else {
turnF(tank,tc);
}
}
}
private function turnF(tank,quadrant:String):void{
if(quadrant=="LU"){
tank.nextDirection = int(90*Math.random());
} else if(quadrant=="LD"){
tank.nextDirection = 270+int(90*Math.random());
} else if(quadrant=="RU"){
tank.nextDirection = 90+int(90*Math.random());
} else {
tank.nextDirection = 180+int(90*Math.random());
}
}
private function moveBackF(e:TimerEvent):void{
var tank:EnemyTank = EnemyTank(e.target.scope);
directionChangeUpdateF(tank);
tank.moveBackBool = false;
tank.currentDirection = (180+tank.currentDirection)%360;
}
private function directionChangeF(e:Event):void{
for(var i:int=enemyA_len-1;i>=0;i– –){
var tank:EnemyTank = EnemyTank(enemyA[i]);
The Tank Combat Game for the iPad
if(!tank.moveBackBool && Math.abs(tank.
currentDirection-tank.nextDirection)>rotationRate/2){
// transition from currentDirection to
// nextDirection the "short" way.
if(tank.currentDirection<=tank.
nextDirection){
if(tank.nextDirection-tank.
currentDirection<=180){
// increment currentDirection
tank.currentDirection
+=rotationRate;
} else {
// decrement currentDirection.
// (eg, currentDirection=5 &
// nextDirection=355, go the
// "short" way, 5 to 355
tank.currentDirection =(tank.
currentDirection-rotationRate+360)%360;
}
} else {
// currentDirection>nextDirection
if(tank.currentDirectiontank.nextDirection<180){
// decrement currentDirection
tank.currentDirection=rotationRate;
} else {
// increment currentDirection
tank.currentDirection = (tank.
currentDirection+rotationRate)%360;
}
}
directionChangeUpdateF(tank);
}
}
}
private function directionChangeUpdateF(tank:EnemyTank):void{
tank.distance = speed;
295
296
Chapter 8 n Developing and Distributing Games for iOS Devices
tank.startX = tank.x;
tank.startY = tank.y;
}
/////////////////// end enemy tank ///////////////////////
private function cleanupF(e:Event):void{
this.removeEventListener(Event.ENTER_FRAME,shellLoopF,
false);
this.removeEventListener(Event.ENTER_FRAME,rotatePlayerTurretF,false);
this.removeEventListener(Event.ENTER_FRAME,directionChangeF,false);
this.removeEventListener(Event.ENTER_FRAME,rotateEnemyTurretF,false);
this.removeEventListener(Event.ENTER_FRAME,moveEnemyTankF,
false);
this.removeEventListener(Event.ENTER_FRAME,
movePlayerTankF,false);
accelerometer.removeEventListener(AccelerometerEvent.
UPDATE, controlChangePlayerF,false);
if(player.parent){
player.parent.removeEventListener(TouchEvent.
TOUCH_BEGIN, startDragF,false);
player.parent.removeEventListener(TouchEvent.
TOUCH_END, stopDragF,false);
player.parent.removeChild(player);
}
this.removeEventListener(Event.ENTER_FRAME,shellLoopF);
for(var i:int=enemyA.length-1;i>=0;i– –){
enemyA[i].moveBackTimer.stop();
enemyA[i].moveBackTimer.removeEventListener(TimerEvent.TIMER,moveBackF,false);
if(enemyA[i].stage){
enemyA[i].parent.removeChild(enemyA[i]);
}
}
newDirectionTimer.stop();
newDirectionTimer.removeEventListener(TimerEvent.TIMER,newDirectionF,false);
enemyA.length = 0;
tankA.length = 0;
}
}
}
The
Controller_Pool
class suppies the Controller instance.
The Tank Combat Game for the iPad
Controller_Pool
package com.kglad{
public class Controller_Pool {
private static var pool:Vector.<Controller>;
public static function init(poolSize:int):void {
pool = new Vector.<Controller>(poolSize);
pool[0] = new Controller();
}
public static function retrieveF():Controller {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new Controller();
}
}
public static function returnF(controller:Controller):void {
pool.push(controller);
}
}
}
I needed to extend the
Timer
class with a Timer that could track its scope.
TimerExt
package com.kglad{
import flash.utils.Timer;
import flash.display.MovieClip;
public class TimerExt extends Timer {
private var _scope:MovieClip
public function TimerExt(delay:Number, repeatCount:int = 0){
super(delay,repeatCount);
}
public function set scope(mc:MovieClip):void{
_scope = mc;
}
public function get scope():MovieClip{
return _scope;
}
}
}
The base
Tank
class that
PlayerTank
and
EnemyTank
extend, follows.
297
298
Chapter 8 n Developing and Distributing Games for iOS Devices
Tank
package com.kglad {
// All the code to control the tanks is in the Controller class.
// That change did not enhance encapsulation, but it did increase
// efficiency because I was able to eliminate enterFrame listeners
// added to each tank.
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
public class Tank extends MovieClip{
internal var xLimit:int;
internal var yLimit:int;
internal var hits:int;
internal var gunL:Number;
internal var prevX:Number;
internal var prevY:Number;
internal var shellA:Array = [];
// array of foes
internal var foeA:Array = [];
// array of friends
internal var friendA:Array = [];
public function Tank(_xLimit:int,_yLimit:int) {
xLimit = _xLimit;
yLimit = _yLimit;
gunL = this.turret_mc.width-this.turret_mc.top_mc.width/2;
// I cached the turrets but didn’t feel caching the tanks
// would be helpful because the tank bitmap would need to
// be re-cached with every turret rotation.
this.turret_mc.cacheAsBitmap = true;
this.turret_mc.cacheAsBitmapMatrix = new Matrix();
// None of the tanks or their children need to interact
// with the mouse.
this.mouseEnabled = false;
this.mouseChildren = false;
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,
true);
this.addEventListener(Event.REMOVED_FROM_STAGE,
removedF,false,0,true);
}
The Tank Combat Game for the iPad
private function init(e:Event):void{
// initialize hits
hits = 0;
}
private function removedF(e:Event):void{
for(var i:int=shellA.length-1;i>=0;i– –){
if(shellA[i].parent){
shellA[i].parent.removeChild(shellA[i]);
}
}
shellA.length = 0;
}
}
}
The
PlayerTank
class handles the custom cursor.
PlayerTank
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
public class PlayerTank extends Tank {
internal var cCursor:CustomCursor;
// Number of foes shot.
internal var score:int;
public function PlayerTank(_xLimit:int,_yLimit:int) {
super(_xLimit,_yLimit);
cCursor = new CustomCursor();
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,
true);
this.addEventListener(Event.REMOVED_FROM_STAGE,removedP,
false,0,true);
}
private function init(e:Event):void{
addCustomCursorF();
}
private function addCustomCursorF():void{
// There’s no mouse to hide and show. The only thing
// this class controls is adding and removing the
// CustomCursor.
this.parent.addChild(cCursor);
}
299
300
Chapter 8 n Developing and Distributing Games for iOS Devices
private function removeCustomCursorF():void{
this.parent.removeChild(cCursor);
}
private function removedP(e:Event):void{
if(cCursor.stage){
removeCustomCursorF();
}
}
}
}
The PlayerTank_Pool class supplies two player tank instances: one for
one for CombatView.
IntroView
and
PlayerTank_Pool
package com.kglad{
// This class must pool two different PlayerTanks, one for
// IntroView and one for CombatView, because of the different
// boundaries in those two views.
public class PlayerTank_Pool {
private static var xLimitIntro:int;
private static var yLimitIntro:int;
private static var xLimitCombat:int;
private static var yLimitCombat:int;
// Two vector pools.
private static var introPool:Vector.<PlayerTank>;
private static var combatPool:Vector.<PlayerTank>;
public static function
init(_xLimitIntro:int,_yLimitIntro:int,_xLimitCombat:int,_yLimitCombat:int):void {
// Assign the boundary limits for both views.
xLimitIntro = _xLimitIntro;
yLimitIntro = _yLimitIntro;
xLimitCombat = _xLimitCombat;
yLimitCombat = _yLimitCombat;
introPool = new Vector.<PlayerTank>(1);
combatPool = new Vector.<PlayerTank>(1);
// Populate the two vectors with the appropriate PlayerTank
introPool[0] = new PlayerTank(xLimitIntro,yLimitIntro);
combatPool[0] = new PlayerTank(xLimitCombat,yLimitCombat);
}
// Two different retrieve and return functions, but otherwise
// this is a typical pool class.
public static function retrieveIntroF():PlayerTank {
if (introPool.length>0) {
The Tank Combat Game for the iPad
return introPool.pop();
} else {
// this branch should not execute.
return new PlayerTank(xLimitIntro,yLimitIntro);
}
}
public static function returnIntroF(tank:PlayerTank):void {
introPool.push(tank);
}
public static function retrieveCombatF():PlayerTank {
if (combatPool.length>0) {
return combatPool.pop();
} else {
// this branch should not execute.
return new PlayerTank(xLimitCombat,yLimitCombat);
}
}
public static function returnCombatF(tank:PlayerTank):void {
combatPool.push(tank);
}
}
}
The
EnemyTank
class handles the color for the enemy tanks.
EnemyTank
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.utils.Timer;
public class EnemyTank extends Tank {
internal var moveBackBool:Boolean;
internal var moveBackTimer:Timer;
internal var currentDirection:int;
internal var nextDirection:int;
internal var distance:int;
internal var startX:int;
internal var startY:int;
public function EnemyTank(_xLimit:int,_yLimit:int) {
super(_xLimit,_yLimit);
// colorF() is the only thing left in EnemyTank
// besides the variable declarations.
301
302
Chapter 8 n Developing and Distributing Games for iOS Devices
colorF();
}
private function colorF():void{
var ct:ColorTransform = new ColorTransform();
ct.color = 0x990000;
this.base_mc.transform.colorTransform = ct;
ct.color = 0x660000;
this.turret_mc.top_mc.transform.colorTransform = ct;
}
}
}
The
EnemyTank_Pool
class supplies up to 20 enemy tanks.
EnemyTank_Pool
package com.kglad{
public class EnemyTank_Pool {
private static var xLimit:int;
private static var yLimit:int;
private static var pool:Vector.<EnemyTank>;
public static function init(poolSize:int,_xLimit:int,_yLimit:int):
void {
xLimit = _xLimit;
yLimit = _yLimit;
pool = new Vector.<EnemyTank>(poolSize);
for(var i:int=poolSize-1;i>=0;i– –){
pool[i] = new EnemyTank(xLimit,yLimit);
}
}
public static function retrieveF():EnemyTank {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new EnemyTank(xLimit,yLimit)
}
}
public static function returnF(tank:EnemyTank):void {
pool.push(tank);
}
}
}
The
GameOverView
class handles the view presented after a combat round completes.
The Tank Combat Game for the iPad
GameOverView
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TouchEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.Font;
import flash.utils.getTimer;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
public class GameOverView extends MovieClip {
var tf:TextField;
public function GameOverView() {
// Code that doesn’t need to be re-executed when this
// instance is added to the stage has been moved to the
// constructor.
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,
true);
var tfor:TextFormat = new TextFormat();
var verdana:Verdana = new Verdana();
tfor.font = verdana.fontName;
tfor.size = 14;
tf= new TextField();
tf.embedFonts = true;
// Use defaultTextFormat when assigning a TextFormat
// instance before text is assigned. After text is
// assigned, use the setTextFormat() method.
tf.defaultTextFormat = tfor;
tf.multiline = true;
tf.width = 400;
tf.autoSize = "left";
tf.y = 200;
}
private function init(e:Event):void{
listenersF();
if(Data.enemiesRemaining==0){
var s:String = "You won!!\n\nYou had ";
} else {
303
304
Chapter 8 n Developing and Distributing Games for iOS Devices
s = "You lost!\n\nYou had ";
}
s+= Data.score+" enemy hits\nand you were shot "+Data.
playerHits+" times.\n\nThere were "+Data.enemiesRemaining+" enemies remaining.";
addChild(tf);
tf.x = (stage.stageWidth-tf.width)/2;
tf.text = s;
}
private function listenersF():void{
replay_mc.addEventListener(TouchEvent.TOUCH_TAP,replayF,false,0,true);
replay_mc.buttonMode = true;
}
private function cleanupF(e:Event):void{
replay_mc.removeEventListener(TouchEvent.TOUCH_TAP,
replayF,false);
}
private function replayF(e:Event):void{
dispatchEvent(new Event("replayE"));
}
}
}
The
GameOverView_Pool
supplies the one
GameOverView
instance.
GameOverView_Pool
package com.kglad{
public class GameOverView_Pool {
private static var pool:Vector.<GameOverView>;
public static function init(poolSize:int):void {
pool = new Vector.<GameOverView>(poolSize);
for(var i:int=poolSize-1;i>=0; i– –){
pool[i] = new GameOverView();
}
}
public static function retrieveF():GameOverView {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new GameOverView();
}
}
The Tank Combat Game for the iPad
public static function returnF(view:GameOverView):void {
pool.push(view);
}
}
}
The Data class gets and sets the parameters that customize the game and the values
presented by GameOverView.
Data
package com.kglad {
public class Data {
// variables with default values below
// The only changes in this class are the changes needed
// to use accelerometerEvent properties for _forward,
// _back, _left, _right
private static var _forward:Number ;
private static var _back:Number;
private static var _left:Number;
private static var _right:Number;
private static var _freeForAll:Boolean;
private static var _tankSpeed:int;
private static var _rotationRate:int = 5;
private static var _shellSpeed:int;
private static var _maxShells:int
private static var _enemyNum:int;
private static var _maxHits:int;
private static var _enemyEvasiveness:int;
private static var _startTime:int
private static var _combatEndDelay:int;
// Default values for above variables. Assigned in
// resetAllF() below
private static var defaultValueA:Array = [false,.5,.7,-.1,.1,4,10,1,
5,1,3,3,3];
public static var defaultVariableA:Array =
["_freeForAll","_forward","_back","_left","_right","_tankSpeed","_shellSpeed",
"_maxShells","_maxHits","_enemyNum","_enemyEvasiveness","_startTime","_combatEndDelay"];
private static var _playerHits:int;
private static var _score:int;
private static var _enemiesRemaining:int;
public function Data() {
// constructor code
}
public static function set forward(n:Number):void{
305
306
Chapter 8 n Developing and Distributing Games for iOS Devices
_forward = n;
}
public static function set back(n:Number):void{
_back = n
}
public static function set left(n:Number):void{
_left = n;
}
public static function set right(n:Number):void{
_right = n
}
public static function get forward():Number{
return _forward;
}
public static function get back():Number{
return _back
}
public static function get left():Number{
return _left;
}
public static function get right():Number{
return _right;
}
public static function set playerHits(n:int):void{
_playerHits = n;
}
public static function get playerHits():int{
return _playerHits;
}
public static function set score(n:int):void{
_score = n;
}
public static function get score():int{
return _score;
}
public static function set enemiesRemaining(n:int):void{
_enemiesRemaining = n;
}
public static function get enemiesRemaining():int{
return _enemiesRemaining;
}
public static function set freeForAll(b:Boolean):void{
_freeForAll = b;
}
The Tank Combat Game for the iPad
public static function get freeForAll():Boolean{
return _freeForAll;
}
public static function set tankSpeed(n:int):void{
_tankSpeed = n;
}
public static function get tankSpeed():int{
return _tankSpeed;
}
public static function get rotationRate():int{
return _rotationRate;
}
public static function set shellSpeed(n:int):void{
_shellSpeed = n;
}
public static function get shellSpeed():int{
return _shellSpeed;
}
public static function set maxShells(n:int):void{
_maxShells = n;
}
public static function get maxShells():int{
return _maxShells;
}
public static function set maxHits(n:int):void{
_maxHits = n;
}
public static function get maxHits():int{
return _maxHits;
}
public static function set enemyNum(n:int):void{
_enemyNum = n;
}
public static function get enemyNum():int{
return _enemyNum;
}
public static function set enemyEvasiveness(n:int):void{
_enemyEvasiveness = n;
}
public static function get enemyEvasiveness():int{
return _enemyEvasiveness;
}
public static function set startTime(n:int):void{
_startTime = n;
307
308
Chapter 8 n Developing and Distributing Games for iOS Devices
}
public static function get startTime():int{
return _startTime;
}
public static function set combatEndDelay(n:int):void{
_combatEndDelay = n;
}
public static function get combatEndDelay():int{
return _combatEndDelay;
}
public static function resetAllF():void{
for(var i:int=defaultValueA.length-1;i>=0;i– –){
Data[defaultVariableA[i]] = defaultValueA[i];
}
}
}
}
Testing an iOS Game
At the time of this writing, there is no iOS emulator. However, Flash Pro CS6 has a
worthwhile mobile simulator you can use to simulate certain aspects of the mobile
(including iOS and Android devices) experience. Specifically, the simulator allows
you to test touches, gestures, acceleration of the device itself (almost as if you were
holding it and rotating and tilting it), and geolocation.
It is far from ideal, mainly because it doesn’t emulate iOS performance, but it is still
very useful for testing and is a major timesaver. Also, you cannot test (or, at least, it
is difficult to test) more than one of the three mobile-specific features (Accelerometer, Touch and Gesture, and Geolocation) at any one time.
Hopefully, Adobe will expand the simulator’s capabilities to simulate mobile devices
in future releases of Flash Pro. (That feature request has been logged.)
To use the simulator, in Flash Pro CS6, click Control > Test Movie > In Air Debug
Launcher (Mobile).
You should see two panels open.
n
The Simulator Controller panel (see Figure 8.1)
Testing an iOS Game
The
Accelerometer
menu
The Touch and
Gesture menu
The
Geolocation
menu
Figure 8.1
The mobile Simulator panel with three accordion menus.
Source: Adobe Systems Incorporated.
309
310
Chapter 8 n Developing and Distributing Games for iOS Devices
n
The game in an Air display (see Figure 8.2)
Figure 8.2
Tank combat in the Air Debug Launcher.
Source: Adobe Systems Incorporated.
When the Simulator panel opens, the Accelerometer menu is expanded. If you play
with the X-axis, Y-axis, and Z-axis sliders, you can see the tank moving and responding to the accelerometer. But actually making the tank do what you want is close to
impossible. We’ll fix that.
First, reset the simulated device’s position by clicking the Reset icon (see Figure 8.3).
Testing an iOS Game
The Accelerometer
menu’s settings icon
The Accelerometer
menu’s reset icon
Figure 8.3
Clicking the Accelerometer menu’s Reset icon reorients the simulated device’s position.
Source: Adobe Systems Incorporated.
Then drag the thumb of the X-slider to rotate the device about the X-axis, setting the
X-rotation to about −50 degrees. (Alternatively, you can enter −50 into the input field
to the right of the X-slider and press the Enter key.) Click to expand the Touch and
Gesture menu and check Click and Drag (if it’s not already checked).
You will now be able to simulate a touch event on the game’s Air display. As you mouse
over the Air display, you will see your mouse pointer change to indicate that it will simulate a touch event when you click. Click the Forward button in the Air display.
You have now defined the X-rotation needed to move the tank forward. Now define
the X-rotation needed to move the tank back.
Expand the Accelerometer menu and change the X-rotation to about −20 degrees.
Expand the Touch and Gesture menu and then click the Back button in the Air
311
312
Chapter 8 n Developing and Distributing Games for iOS Devices
display. Now, unless your tank is in a corner, you should be able to expand the
Accelerometer menu and drag the X-axis slider to the left and right, making the
tank go forward, stop, and go back, and feel as if you have fair control of the tank.
With the tank stopped (in other words, set X-rotation to between −50 and
−20 degrees), use the Z-slider to customize the tank’s left and right rotations. Drag
the Z-slider to the right to about 30 degrees, expand the Touch and Gesture menu,
and then click the left button in the Air display.
You have defined the Z-rotation needed to rotate your tank left. Likewise, to customize the rotate tank right feature, expand the Accelerometer menu, drag the Z-slider to
about −30 degrees, expand the Touch and Gesture menu, and click the right button
in the Air display.
It isn’t very convenient to go back and forth among the three menus, but often it’s
more convenient than going back and forth between your development platform and
an iPad or other device. Playing the game using the simulator is impossible because
you must use the accelerometer and touch simulations at the same time, and that’s
not possible using the Flash Air Debug Launcher (Flash ADL).
Once you have finished debugging any major part of your game using the Flash
ADL, you should test on the target platform and debug any problems you see when
testing on the deployment platform. Hopefully, you will have debugged most of the
problems using the Flash ADL so you won’t need to repeatedly publish your game,
add your game to your target device, and then test on your target device. That
sequence of steps is very time-consuming.
I just want to mention one more tip about the Simulator Controller. When using the
Accelerometer menu, if you want the Air display to reflect a vertical and horizontal
device orientation as you change the Z-rotation in the Simulator Controller, you
must check the Auto Orientation option in the Settings subpanel. You open that subpanel by clicking the Settings icon (to the right of the Reset icon). Refer to Figure 8.3.
The next section will cover how to publish an .ipa file, add it to your iTunes, and
load it onto a target device.
Publishing Your Game for iOS
To publish a game you are developing for iOS, you will need:
n
A development Certificate (a secure certificate that allows you to develop iOS
apps/games)
n
For each app/game, a development Provisioning Profile
Publishing Your Game for iOS
Using both of those, you can publish apps/games that can be tested—but only on
devices you designate to the authorities at Apple and only after you pay $99 per
year (for your Developer License) and navigate an overly complicated process to
obtain those two files.
Then, if you want to submit an app/game to the Apple Store (or you want to distribute your game to users with unknown devices), you must also obtain:
n
A distribution Certificate (a secure certificate that allows you to distribute iOS
apps/games)
n
For each app/game, a distribution Provisioning Profile
You must complete the following steps to obtain the first two files that are required
to develop an iOS game. This list is not intended to be casually read. Dog-ear or otherwise note this page and use it as a reference to obtain the files you need when you
need them. I envision you and me, the next time I need one of the above files, navigating to the Apple Developer page and following the relevant steps for the needed
file(s).
You won’t need to follow all 29 steps every time you want to create a new app/game.
Some steps (1–13) only need to be followed once per year (unless you develop more
than 100 apps/games in less than a year). And some steps (14–16) are also less frequently needed than the main steps (17–29), which you must follow each time you
want to develop a new app/game.
n
You need to repeat the first 13 steps only once each year, when your Apple
Developer License expires and you need another Certificate file. You will need to
use a Mac or virtual Mac computer (Mac OS running on a PC) for some of
these steps.
n
You will need to follow Steps 14, 15, and 16 to add a new device for testing. You
can do that anytime your license is valid. You only need to use a Mac or virtural
Mac computer if you need to determine a device’s UDID (Unique Device
Identifier).
n
You need to follow Steps 17 to 29 each time you want to develop an additional
app/game with a new App ID. You can use a PC for all these steps.
1. Go to https://developer.apple.com/devcenter/ios/index.action using a Mac or a
virtual Mac.
2. Log in or, if you aren’t registered, register and then log in. (Registration costs
$99/year.)
313
314
Chapter 8 n Developing and Distributing Games for iOS Devices
3. Click on the iOS Dev Center link.
4. Click on the iOS Provisioning Portal link. (See Figure 8.4.)
The Request Certificate button to start the process
of obtaining your development certificate
Figure 8.4
iOS Provisioning Portal page showing left panel links Home, Certficates, Devices, App IDs, Provisioning,
and Distribution. You will need to use all but Home and Distribution.
Source: Apple® Inc.
5. Click on the Certificates link in the left panel.
6. Click on the Development tab in the right panel.
7. Click on the Request Certificate button in the right panel.
8. Follow the directions using Keychain Access to create your development Certificate. (See Figure 8.5.) Apple has easy-to-follow directions to create a Certificate
Signing Request that is used to create your Certificate. The steps needed to create a Certificate Signing Request appear in the right panel after Step 7. The only
part I stumble on is when instructed to open Certificate Assistant. You will find
it at the top left of your Mac’s screen after clicking Keychain Access, not in the
Keychain Access window.
Publishing Your Game for iOS
Figure 8.5
After clicking Request Certificate, you’ll see this helpful page of instructions.
Source: Apple® Inc.
9. The right panel should change to show that your Certificate is pending issuance.
Refresh the page, and you should see that your Certificate is issued and ready for
download. (See Figure 8.6.)
315
316
Chapter 8 n Developing and Distributing Games for iOS Devices
Figure 8.6
After following the directions for using Keychain Access and uploading your Certificate Signing Request, you
should see this page with Download replaced by Pending. Refresh the page to see the Download button.
Source: Apple® Inc.
10. Download your ios_development.cer by clicking the Download button.
11. Download an AppleWWDRCA.cer by clicking the *If You Do Not Have the
WWDR Intermediate Certificate Installed, Click Here to Download Now link.
12. Open both files in Keychain Access (using the login Keychain).
13. Right-click your private key in Keychain Access (you may need to expand the
iPhone Developer certificate shown in your Keychain Access window) and
click Export. Make sure File Format is Personal Information Exchange (.p12)
and click Save. You will be prompted for a password that you must remember.
You will use that password each and every time you publish an iOS game, and
you will use your .p12 (development Certificate) file for all the apps/games you
develop (until your Certificate expires and you pay another $99 and follow these
steps again).
Congratulations, you have completed the steps needed to obtain the first of the
four files mentioned previously!
14. Click on the Devices link in the left panel. Then click the Add Devices button
toward the upper right to add devices that will be used to test your game. You
Publishing Your Game for iOS
usually cannot test your game on a device unless you add it here or your device
is jailbroken. There is an exception (using a distribution Certificate and ad hoc
distribution Provisioning Profile) covered in the next section. (See Figure 8.7.)
Figure 8.7
After clicking Devices, you should see this.
Source: Apple® Inc.
15. You will need to enter the device name (pick something sensible) and the device
ID. The device ID is the UDID that will be used for testing the app/game. To
find a device’s UDID, follow the instructions in Steps 16a, 16b, 16c, and 16d.
Otherwise, you can skip those steps. (See Figure 8.8.)
317
318
Chapter 8 n Developing and Distributing Games for iOS Devices
Figure 8.8
After clicking the Add Devices button, you should see this, where you enter an app name and UDID and
click the plus sign to enter more names and UDIDs or click Submit.
Source: Apple® Inc.
16a. Open iTunes and connect your device to your Mac, virtual Mac, or PC with
iTunes installed.
16b. In the left panel of iTunes, find your device and click on it to reveal summary
info about it in the right panel. (Make sure the Summary tab is selected to see
that info.)
16c. Click the device’s serial number (in the right panel) to reveal the device’s identifier. That identifier is the UDID. Copy it to your clipboard and paste it.
16d. Click the plus sign to add more devices or repeatedly click Add Devices. (You
are limited to 100 testing devices per Certificate.)
17. Click the App IDs link in the left panel.
18. Click the New App ID button (toward the upper right). (See Figure 8.9.)
Publishing Your Game for iOS
Figure 8.9
After clicking App IDs on the left and New App ID on the right, you should see this, where you enter a
description, Bundle Seed ID, and Bundle Identifier.
Source: Apple® Inc.
19. Enter your App description (using only alphanumeric characters).
20. Select Team ID (unless you already have a Bundle Seed ID that you wish to
reuse for related apps/games) in the Bundle Seed ID combobox.
21. Enter your Bundle Identifier. If you have a website, use com.yourwebsitename.
yourappname for your Bundle Identifier.
22. Click Submit.
23. You should be back at the main App IDs link, and your new App ID (along
with all of your other App IDs) should be listed. (See Figure 8.10.)
319
320
Chapter 8 n Developing and Distributing Games for iOS Devices
Figure 8.10
You should see this after submitting your new App ID.
Source: Apple® Inc.
Publishing Your Game for iOS
24. Click the Configure link if you want to add Apple Push Notification service and
follow that multistep procedure, which has helpful instructions.
25. Click Provisioning in the left panel and, in the right panel, confirm that the Distribution tab is selected and click the New Profile button toward the upper right.
(See Figure 8.11.)
Figure 8.11
You should see this after clicking New Profile.
Source: Apple® Inc.
26. Enter a Provisioning Profile name, check whose Certificate will be used with the
Provisioning Profile, select the desired App ID from the combobox, check the
device that will be used for testing during development, and click Submit.
27. You should be back at the Provisioning link/Development tab, and the status
(fourth column) of your new profile is probably pending. (Mine always is.) See
Figure 8.12, where I just added a “switcher” Profile.
321
322
Chapter 8 n Developing and Distributing Games for iOS Devices
Figure 8.12
You should see this after submitting a new developer Provisioning Profile.
Source: Apple® Inc.
28. Refresh the page. Your Provisioning Profile’s status should change to active, and
a Download button should appear. If it doesn’t, go have a frappe and refresh the
page when you return. (See Figure 8.13.)
Publishing Your Game for iOS
Figure 8.13
You should see this after refreshing the Provisioning page.
Source: Apple® Inc.
29. Click the Download button to download your Provisioning Profile. Save this to
the directory where you are developing your app/game. Each app/game will have
its own Provisioning Profile.
With your development certificate and development Provisioning Profile, you’re
ready to publish an iOS game from Flash Pro.
After exploring how to publish an iOS game using your .p12 development Certificate
and your development Provisioning Profile, I will list the steps needed for you to
obtain a distribution Certificate and distribution Provisioning Profile. Those steps
are similar to the steps just listed.
Air for iOS Settings: General Tab
Open Flash and load the FLA that corresponds to the Provisioning Profile you just
created or create a new FLA that will start the game that corresponds to your Provisioning Profile.
323
324
Chapter 8 n Developing and Distributing Games for iOS Devices
Click File > Publish Settings > SWF and select the most recent version of Air for iOS
in the Target combobox (see Figure 8.14).
To the right
of the target
combobox is
the Player Settings
icon. Click it to
open the Player
Settings panel.
Figure 8.14
After clicking File > Publish Settings, you should see the Publish Setting panel.
Source: Adobe Systems Incorporated.
To the right of the Target combo box is the Player Settings icon. Click it to open the
Player Settings panel.
If you selected Air for iOS in the Target combo box this panel should be the Air for
iOS Settings panel (see Figure 8.15).
Publishing Your Game for iOS
Figure 8.15
The Air for iOS Settings panel, General tab.
Source: Adobe Systems Incorporated.
Click the General tab if it isn’t already selected. You should see several fields for text
and several comboboxes.
The Output File field contains the name of the .ipa file, which Flash will publish.
That file will be installed on the device for testing.
The App Name field holds the name of your game, which will appear under your
game’s icon when installed on an iOS device (and in iTunes). A limited number of
characters will display.
325
326
Chapter 8 n Developing and Distributing Games for iOS Devices
The Version value doesn’t need to change. That will only become important when
you publish an .ipa file for distribution. We’re still in the development phase.
The Aspect Ratio field should be Auto if you want your game to rotate when the
device is rotated. Otherwise, select Portrait or Landscape.
Check Full Screen and/or Auto orientation if desired. Auto orientation should be
checked if you selected Auto in the Aspect Ratio combobox.
Render Mode is probably the most important option in this panel. What you select
here can make the difference between a poorly performing game and a game with
smooth play. There are some guidelines for what you should select, but none of
them is worthwhile except that if you’re using the Stage3D API, you must select
Direct.
Otherwise, you should test with both CPU and GPU to see which is best. Generally,
if you’re doing bitmap manipulation, GPU will usually be better.
But I don’t think that’s helpful. You won’t care whether performance is usually better
with GPU Render mode. You want to know if performance will be better with GPU
mode. And that is best determined by testing the performance with CPU and then
testing with GPU.
Again, if you’re using Stage3D, choose Render mode Direct. You have no other option
that will work with Stage3D.
If you are not using Stage3D, choose Render mode CPU and test on your target iOS
device. Then choose Render mode GPU and test on your target iOS device. Use the
Render mode that worked best when you tested. There is also an Auto Render mode,
but I believe that’s still the same as CPU mode. Eventually, some sort of Auto Render
mode may be implemented by Adobe (or abandoned), but as of the time of this writing, Auto and CPU mode are the same.
What you select for Device should be obvious. Select iPhone and/or iPad depending
on your target platform.
Select High for Resolution if you’re targeting devices with retina displays and your
game’s performance is satisfactory. Otherwise, select Standard.
And lastly, the Included Files section of this panel should contain two files by default:
the published SWF and an XML file that Adobe calls your application descriptor. The
descriptor contains the information from the Air for iOS Settings panel that you’re
currently editing.
In addition, you can (and should) add any files needed by your game. You don’t need
to add any of your class files because all that code is compiled into your SWF by
Publishing Your Game for iOS
Flash when you publish the SWF. But if you load bitmaps or data files or another
SWF, they should be added to this section.
Also, if you load another SWF and it contains ActionScript, none of the code will
execute. That’s an Apple restriction. So, you are limited to loading SWFs that contain
graphic assets that can be used in your main SWF.
You can click the icon on the left to add individual files, and you can click the icon
on the right to add entire directories of files. The middle icon is used to remove
something you previously added that is no longer needed.
Air for iOS Settings: Deployment Tab
Click the Deployment tab (see Figure 8.16).
Figure 8.16
Air for iOS Settings panel with Deployment tab selected.
Source: Adobe Systems Incorporated.
327
328
Chapter 8 n Developing and Distributing Games for iOS Devices
To the right of the Certificate field is a Browse button that you should click to navigate to your development Certificate .p12 file. Click the file and click Open.
Enter the Certificate password you created in Keychain Access and check Remember
Password for This Session if you do not wish to repeatedly enter your password each
time you publish your game.
To the right of the Provisioning Profile field is another Browse button that you
should click to navigate to your development Provisioning Profile .mobileprovision
file. Click the file and click Open.
Alert: What follows is a little screwy. In the App ID field, enter the Bundle Identifier
(in other words, App ID suffix) you used in Step 21. If you click the App ID or Provisioning link at the iOS Provisioning Portal, you will see a Bundle Seed ID (an
alphanumeric string) dot followed by your Bundle Identifier. That concatenated
string is what Apple displays as the App ID. But, for publishing your iOS game,
you only want to use the suffix of that concatenated string.
For example, the Provisioning link at the Provisioning Portal, for my switcher game,
shows 547746GW93.switches (refer to Figure 8.12). The App ID to be used in Flash
is switches. (The fact that switches and switcher are not identical was an inadvertent
mixup on my part.)
Finally, select one of the following iOS deployment option types.
1. Quick publishing for device testing
2. Quick publishing for device debugging
3. Deployment - Ad Hoc
4. Deployment - Apple App Store
Option 1
Use Option 1 for testing on an iOS device. This is the fastest option and will publish
an .ipa file.
Add that .ipa file to your Mac or virtual Mac iTunes Apps and connect your iOS
device to your Mac or virtual Mac. iTunes should detect your device and list it
under Devices. Click your device and click Apps. You should see your app/game
listed. Click it to select it and click Sync to load the app/game to your device.
If you did everything correctly, your app/game will load without a problem. If it
loads, go to your device and test your game.
Otherwise, you will see a more-or-less cryptic error message. If you see an error message, either retrace the steps, especially checking the App ID you entered into the Air
for iOS Settings panel, or copy the error message and search for help using Google.
Publishing Your Game for iOS
Option 2
If you want to test on an iOS device and see trace output in Flash, select Option 2.
You should see a network interface for remote debugging displayed in the combobox.
Copy the IP address if that’s what you’re going to select. After selecting a network
interface, publish your .ipa file.
Follow the same steps listed in Option 1 to load your game onto your iOS device. In
Flash, click Debug > Begin Remote Debug Session > ActionScript 3.0.
Tap your newly loaded game. It may take a minute to start up. When it does, you
should see a Flash Debugger prompt for the IP address or hostname you just copied.
Type the IP address or hostname and tap OK.
If all goes well, you will see trace output from your device using your network connection. If you cannot connect, make sure your iOS device has access to the development computer’s network.
Option 3
If you want to test on devices whose UDID you don’t know (for example, you want
to upload your app/game to a server and let whomever test it), you should use this
option. However, you will need to go back to the iOS Provisioning Portal to get two
more files and use those files in the Certificate and in the Provisioning Profile fields
to publish an ad hoc .ipa file.
One of the files has already been mentioned—an iOS distribution Certificate—and
one I haven’t mentioned—an ad hoc distribution Provisioning Profile. I will list the
steps needed to obtain both of those files in the upcoming “Distributing Your Game
for iOS” section.
Option 4
When your app/game’s development is complete and you’re ready to submit your
app/game to the Apple App Store, you select this option. However, you’ll need to
go back to the iOS Provisioning Portal to get two more files and use those files in
the Certificate and in the Provisioning Profile fields to publish a deployment (Flash
parlance) or distribution (Apple parlance) .ipa.
I’ve already mentioned both of the needed files: a distribution Certificate and a distribution Provisioning Profile. I’ll list the steps needed to obtain both of those files in
the upcoming “Distributing Your Game for iOS” section. For now, let’s finish discussing the Air for iOS Settings panel.
329
330
Chapter 8 n Developing and Distributing Games for iOS Devices
Air for iOS Settings: Icons Tab
Using this tab, you can add icons for your game to use when it is installed on your
target device. You can create these icons using Flash (or your preferred graphics program). To use Flash, add a MovieClip (such as _tank mc), Button, or Graphic to the
stage, size it so the largest dimension (width or height) matches the icon size, rightclick it, and click Export PNG Sequence.
If you used a multiframe object, pick the PNG that you want to use for the icon and
delete the rest. Add the icon sizes used by your target device (http://developer.apple.
com/library/ios/#qa/qa1686/_index.html). (See Tables 8.1 and 8.2.)
Table 8.1 iPad Icon Sizes
Pre-Retina
Retina
Where Used
512 × 512
1024 × 1024
iTunes App Store
144 × 144
72 × 72
Home screen
48 × 48
96 × 96
Spotlight search
29 × 29
58 × 58
Settings
Source: Apple, Inc.
Table 8.2 iPhone Icon Sizes
Pre-Retina
Retina
Where Used
512 × 512
1024 × 1024
iTunes App Store
114 × 114
57 × 57
Home screen
29 × 29
58 × 58
Spotlight search
29 × 29
58 × 58
Settings
Source: Apple, Inc.
Air for iOS Settings: Languages Tab
If you’re using Flash Pro CS6 or better, you will have a Languages tab in your Air for
iOS Settings panel. Otherwise, you won’t and you can ignore this section.
The panel displayed by selecting this tab allows you to indicate which other languages your game supports. That information is added to your application descriptor
and will be displayed by the Apple Store.
Distributing Your Game for iOS
It’s important to notice that no language support is added to your game no matter
what languages you check in this panel. Actually, adding language support is up to
you, the developer.
Distributing Your Game for iOS
To distribute your game outside the Apple Store to users with unknown (to you)
device UDIDs or to add your game to the Apple Store, you need more files than a
development Certificate and a development Provisioning Profile. You will need a distribution Certificate and a distribution Provisioning Profile.
Your distribution Provisioning Profile will indicate whether your game is for users
with unknown UDIDs (called an ad hoc distribution Provisioning Profile) or whether
your game is being submitted to the Apple Store (called a distribution Provisioning
Profile).
Your distribution Certificate is the same whether used with an ad hoc distribution
Provisioning Profile or with a distribution Provisioning Profile. Here is the list of
steps needed to create your distribution Certificate.
1. Go to https://developer.apple.com/devcenter/ios/index.action using a Mac or a
virtual Mac.
2. Log in.
3. Click on the iOS Dev Center link.
4. Click on the iOS Provisioning Portal link (refer to Figure 8.4).
5. Click on the Certificates link in the left panel.
6. Click on the Distribution tab in the right panel.
7. Click on the Request Certificate button in the right panel.
8. Follow the directions using Keychain Access to create your distribution Certificate. (See Figure 8.5.) Apple has easy-to-follow directions to create a Certificate
Signing Request that is used to create your Certificate. The steps needed to create a Certificate Signing Request appear in the right panel after Step 7. The only
part I stumble on is when instructed to open Certificate Assistant. You will find
it at the top left of your Mac’s screen after clicking Keychain Access, not in the
Keychain Access window.
9. The right panel should change to show that your Certificate is pending issuance.
Refresh the page, and you should see that your Certificate is issued and ready for
download (refer to Figure 8.6).
10. Download your ios_distribution.cer by clicking the Download button.
331
332
Chapter 8 n Developing and Distributing Games for iOS Devices
11. Download an AppleWWDRCA.cer by clicking the *If You Do Not Have the
WWDR Intermediate Certificate Installed, Click Here to Download Now link.
12. Open both files in Keychain Access (using the login Keychain).
13. Right-click your private key in Keychain Access (you may need to expand the
iPhone Distribution Certificate shown in your Keychain Access window) and
click Export. Make sure the File Format value is Personal Information Exchange
(.p12) and click Save. You will be prompted for a password that you must
remember. You will use that password each and every time you publish an iOS
game for distribution, and you will use your .p12 (Distribution Certificate) file
for all the apps/games you develop (until your certificate expires and you pay
another $99 and follow these steps again).
Here is the list of steps needed for a distribution Provisioning Profile and an ad hoc
distribution Provisioning Profile.
1. Click the App IDs link in the left panel.
2. Click the New App ID button (toward the upper right). (Refer to Figure 8.9.)
3. Enter your App description (using only alphanumeric characters).
4. Select Team ID (unless you already have a Bundle Seed ID that you wish to
reuse for related apps/games) in the Bundle Seed ID combobox.
5. Enter your Bundle Identifier. If you have a website, use
com.yourwebsitename.yourappname for your Bundle Identifier.
6. Click Submit.
7. You should be back at the main App IDs link, and your new App ID (along
with all your other App IDs) should be listed. (Refer to Figure 8.10.)
8. Click the Configure link if you want to add Apple Push Notification service and
follow that multistep procedure, which has helpful instructions.
9. Click Provisioning in the left panel and, in the right panel, click the Distribution
tab and click the New Profile button toward the upper right. (See Figure 8.17.)
Notice the difference between Figures 8.11 and 8.17 and make sure you click the
Distribution tab so you see something that looks like Figure 8.17.
Distributing Your Game for iOS
Figure 8.17
iOS Provisioning Portal after clicking Provisioning in the left panel and then clicking the Distribution tab
Source: Apple® Inc.
10. Check either App Store or Ad Hoc, depending on which distribution Provisioning Profile you need.
11. Enter a Provisioning Profile name, check whose Certificate will be used with the
Provisioning Profile, and select the App ID from the combobox. Finally, click
Submit.
Note
If you selected App Store in Step 10 in the list of steps needed for a distribution Provisioning Profile and an ad
hoc distribution Provisioning Profile, all the (now optional) devices should be faded, indicating that you cannot
check any device that will be used for testing because this a Provisioning Profile for Apple Store distribution,
not development. However, the Select All link still works for no good reason that I can determine.
12. You should be back at the Provisioning link/Distribution tab, and the status
(fourth column) of your new profile is probably pending. (Mine always is.)
333
334
Chapter 8 n Developing and Distributing Games for iOS Devices
13. Refresh the page. Your Provisioning Profile’s status should change to active, and
a Download button should appear. If it doesn’t, go have a frappe and refresh the
page when you return.
14. Click the Download button to download your Provisioning Profile. Save this to
the directory where you will publish your game for the Apple Store or for ad
hoc distribution.
Now that you have all the files needed, you can publish your game. You will use
exactly the same steps you used for publishing your game for development, except
that instead of using your development Certificate and development Provisioning Profile, you must use your distribution Certificate and (ad hoc or not) distribution Provisioning Profile.
If you used an ad hoc distribution Provisioning Profile, you don’t need to interact
further with Apple. Just distribute your published .ipa to whomever you wish.
According to Apple, you are limited to 100 iOS devices onto which your game can
be installed. I’m not sure how they can ethically check that limit, but it’s Apple. They
like to make and enforce rules.
If you want to add your game to the Apple Store, then you have more steps to follow.
Here are the initial steps.
1. Go to https://itunesconnect.apple.com and log in.
2. Click Manage Your Applications (see Figure 8.18).
Distributing Your Game for iOS
Click Manage
Your Applications
to add your game
to the Apple Store.
Figure 8.18
After logging in to iTunes Connect, you should see this page.
Source: Apple® Inc.
3. Click the Add New App button at the upper left.
There are many more steps, but they are all very clearly explained. Click the question
mark to the right of any field for which you need or want more information.
You’ll need to upload some files (for example, at least one screenshot and at least one
icon), and you’ll need to supply a URL where you offer support for your game. You’ll
also need to indicate pricing information, and you’ll need to supply payment information (if you charge for your game). All of that and more is explained as you work
your way through iTunes Connect.
Also, you can download a clearly written Apple PDF that explains all the requirements and options for developers using iTunes Connect from http://developer.apple.
com/library/ios/iTunesConnectGuide.
335
This page intentionally left blank
Chapter 9
Developing and Distributing
Games for Android Devices
In this chapter, I will examine the steps needed to develop, test, and deploy games for
Android devices. I will start with a puzzle/logic game that I call Switcher.
After going through the code for the Switcher game, I’ll cover technical details about
how to test, publish, and distribute a game developed for an Android device. Those
topics follow the Switcher game code and are the most important parts of this
chapter.
Switcher for Android
Switcher is a simple game that requires the user to tap a series of switches to close a
circuit that passes through each switch. Each switch has three positions: open, ready,
and closed.
If a switch is open, tapping it once changes it to ready. If a switch is ready, tapping it
once changes it to closed. And if a switch is closed, tapping it once changes it to
open.
When all switches are closed, the circuit is closed and a light bulb illuminates. Except
that tapping a switch not only changes the tapped switch’s position, but it also
changes the position of immediately connected switches. (See Figure 9.1.)
337
338
Chapter 9 n Developing and Distributing Games for Android Devices
An open
switch
A ready
switch
A closed
switch
The light
bulb
Figure 9.1
The introduction view of Switcher, showing three switches and one light bulb.
Source: Adobe Systems Incorporated.
This game, like most games of logic, doesn’t contain anything that should stress the
CPU or GPU. The user sees only three screens: the intro view (shown in Figure 9.1),
a game view where more switches are presented, and a game-over view where the
results of the game view are presented.
Switcher for Android
Notice that all three of those classes include a StageOrientationEvent.ORIENTATION_
CHANGE event listener and listener function to control the display when the Android
device is rotated from vertical to horizontal (and vice versa). I believe that is the
only additional thing that the tank combat game did not have.
The document class (Main) controls which view is presented. Because both the intro
view and the game view display working switches, they both use the Controller class,
which controls all the switches and the light.
Just as we did in the tank combat game, we’ll make use of pool classes to help ensure
that there are no memory leaks. And, we’ll use a Data class for the four variables we
share among the intro, game, and game-over views.
Main
package com.kglad {
import flash.display.MovieClip;
import flash.display.StageAlign;
import flash.display.StageScaleMode
import flash.events.Event;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
public class Main extends MovieClip {
private var introView:IntroView;
private var gameView:GameView;
private var gameOverView:GameOverView;
private var controller:Controller;
public function Main() {
//MT.init(this,3);
// The usual inputMode unless you want to detect
// gestures.
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
// Initialize default data (min and max number
// of switches).
defaultDataF();
// These two lines of code are important when the
// stage is rotated. Comment out each of them in
// turn so you can see what happens when they are
// not included, so if you run into the same
// problem, you’ll recognize how to solve the problem.
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
// I used pools for everything displayed. Ensuring
// that there are no memory leaks is much more
// important for mobile games and applications than
339
340
Chapter 9 n Developing and Distributing Games for Android Devices
// web-based games and applications because they
// may not be re-started for months.
TF_Pool.init(1);
// Data.sliderMin and Data.sliderMax were already
// initialized. Because solving these puzzles is
// much easier for puzzles with an even number of
// switches, I only allow an odd number of switches.
Switch_Pool.init(Data.sliderMax*2+1);
Light_Pool.init(1);
Slider_Pool.init(1);
Replay_Btn_Pool.init(1);
// Instead of using a pool to retrieve and return
// one Controller instance (which would have worked
// here), I used a singleton class to limit this
// game to one Controller instance. A singleton class
// can also be used instead of a class with static
// properties (like Data) to share data among other
// classes.
controller = Controller.getInstance(stage);
IntroView_Pool.init(1);
GameView_Pool.init(1);
GameOverView_Pool.init(1);
// Start the game by adding an IntroView instance to
// the display. In this game there are no library
// MovieClips corresponding to the Introview,
// GameView or GameOverView. Everything displayed
// is created or instantiated in those classes.
addIntroViewF();
//MT.init(this,4);
}
private function addIntroViewF():void{
// The usual way to retrieve an instance from a pool.
introView = IntroView_Pool.retrieveF();
// Add a listener to trigger the display of the
// next view (a GameView instance).
introView.addEventListener("startGameE",startGameF,false,0,true);
addChild(introView);
}
private function startGameF(e:Event):void{
removeIntroViewF();
addGameViewF();
}
Switcher for Android
private function removeIntroViewF():void{
// The usual way to return an instance to a pool.
// Return the instance before removing from the
// display. I had some trouble with the switches
// because I wanted them all to return to their
// closed state when they were returned to their
// pool, and that didn’t work correctly unless
// they were still in the display.
IntroView_Pool.returnF(introView);
removeChild(introView);
introView.removeEventListener("startGameE",startGameF,false);
}
// The rest all follow the same format. Only the
// names change.
private function addGameViewF():void{
gameView = GameView_Pool.retrieveF();
gameView.addEventListener("gameOverE",gameOverF,false,0,true);
addChild(gameView);
}
private function gameOverF(e:Event):void{
removeGameViewF();
addGameOverViewF();
}
private function removeGameViewF():void{
GameView_Pool.returnF(gameView);
removeChild(gameView);
gameView.removeEventListener("gameOverE",gameOverF,false);
}
private function addGameOverViewF():void{
gameOverView = GameOverView_Pool.retrieveF();
gameOverView.addEventListener("replayE",replayF,false,0,true);
addChild(gameOverView);
}
private function replayF(e:Event):void{
removeGameOverViewF();
addIntroViewF();
}
private function removeGameOverViewF():void{
GameOverView_Pool.returnF(gameOverView);
removeChild(gameOverView);
gameOverView.removeEventListener("replayE",replayF,false);
}
private function defaultDataF():void{
// default Data values
341
342
Chapter 9 n Developing and Distributing Games for Android Devices
Data.init();
}
}
}
IntroView
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.text.TextField;
import flash.events.StageOrientationEvent;
import flash.events.TouchEvent;
public class IntroView extends MovieClip {
private var instructionsS:String;
private var tf:TextField;
private var switchNum:int = 3;
private var switchA:Vector.<Switch>;
private var light:Light;
private var i:int;
private var controller:Controller;
private var slider:Slider;
public function IntroView() {
// This part of the instructions string never changes
// so it can be added to the constructor, which executes
// only once when this instance is added to its pool.
// The last part of the string uses the number of
// switches, so that has to be updated dynamically.
instructionsS = "Tap a switch to change it from open to ready.
Tap it again to change it from ready to closed and tap it again to open the switch. ";
instructionsS += "Except, tapping a switch not only changes the
tapped switch, it will change any switch immediately connected to the tapped switch.\n
\n";
instructionsS += "The goal is to close all switches thereby closing the circuit and lighting the bulb. You can practice on this screen but three
switches is no challenge.\n\n";
instructionsS += "Pick a more challenging number of switches to
test your capacity for logic.";
// The usual initializer and clean-up functions
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,
true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanUpF,
false,0,true);
}
Switcher for Android
private function init(e:Event):void{
// A vector of the switches to allow the game player
// to test how the switches work.
switchA = new Vector.<Switch>(switchNum);
// A listener to update the display if the device
// is rotated
stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGE,
orientationChangeF,false,0,true);
// Add the textfield used to display the instructions
tfF();
// Add text to the instructions textfield
tfTextF();
// Add the switches used in the IntroView instance
switchesF();
// Add the light
lightF();
// Retrieve a controller and send references to the
// switches and light to the controller.
controllerF();
// Add the slider used to control the number of switches
// in the GameView instance
sliderF();
}
private function tfF():void{
tf = TF_Pool.retrieveF();
tf.y = 10;
tf.x = 10;
addChild(tf);
}
private function tfTextF():void{
// The last paragraph of the instructions is dynamic,
// so I need to remove and re-add it when the user
// changes the number of switches
instructionsS =
instructionsS.substr(0,instructionsS.lastIndexOf("\n\nCurrently "));
instructionsS += "\n\nCurrently "+Data.switchNum+" switches are
selected. When you are ready, tap the light."
tf.text = instructionsS;
}
private function switchesF():void{
// Here is where the switches are retrieved
// and added.
for(i=switchNum-1;i>=0;i––){
343
344
Chapter 9 n Developing and Distributing Games for Android Devices
switchA[i] = Switch_Pool.retrieveF();
addChild(switchA[i]);
// The switches all start in the closed position.
// That’s not important when there are only 3
// switches because no matter what initial state
// the switches are in, there is always a solution:
// move them all to the closed state. But when
// there are more than 3, there are starting
// states that have no solution. Avoiding
// unsolvable states is done by starting all
// the switches in the closed state (i.e.,
// starting with a solved puzzle) and then
// clicking each switch either 0, 1, or 2
// times to obtain the puzzle start. There
// is always a solution when creating a puzzle
// this way. That solution is explained in
// Controller. The switches layout is done in
// controller.
}
}
private function lightF():void{
light = Light_Pool.retrieveF();
// Use the light as a button to start the game.
light.addEventListener(TouchEvent.TOUCH_TAP,startGameF,false,0,true);
addChild(light);
}
private function controllerF():void{
// This is how to retrieve the only controller
// instance when Controller is a singleton
controller = Controller.getInstance();
// Use controller’s init() function to send switches
// and light references and to indicate how from the
// screen-top to start the display of the switches.
controller.init(switchA,light,tf.height+tf.y);
}
private function sliderF():void{
// I’m using the same slider here that I made
// for tank combat.
slider = Slider_Pool.retrieveF();
slider.x = 10;
slider.y = stage.fullScreenHeight-slider.height;
addChild(slider);
// Assign slider’s min and max properties and
// touch listeners
Switcher for Android
slider.min = Data.sliderMin;
slider.max = Data.sliderMax;
slider.addEventListener(TouchEvent.TOUCH_BEGIN,explanationF,
false,0,true);
slider.addEventListener(TouchEvent.TOUCH_END,explanationF,
false,0,true);
slider.addEventListener(TouchEvent.TOUCH_MOVE,sliderChangeF,
false,0,true);
// Because I only want to allow an odd number of
// switches, I convert the slider.value to the
// number of switches by multiplying by 2 and
// adding one.
// i.e., Data.switchNum = 2*slider.value+1;
// Setting the value property of slider given
// Data.switchNum requires the inverse, subtract
// 1 and divide the result by 2.
slider.value = (Data.switchNum-1)/2;
}
private function explanationF(e:TouchEvent):void{
// TouchEvent.TOUCH_BEGIN and TouchEvent.TOUCH_END
// events use this listener function
if(e.type==TouchEvent.TOUCH_BEGIN){
tf.text = "Adjust the number of switches from "+(Data.
sliderMin*2+1)+" to "+(Data.sliderMax*2+1)+". Then tap the light to start.\n
\nCurrently the number of switches is "+(2*int(e.currentTarget.value)+1);
} else {
// On TouchEvent.TOUCH_END, display the text
// in tfTextF()
tfTextF();
}
}
private function sliderChangeF(e:Event):void{
// Update Data.switchNum when the slider changes.
tf.text = "The current number of switches is
"+(2*int(e.currentTarget.value)+1)+" out of a maximum of "+(2*Data.sliderMax+1);
Data.switchNum = 2*int(e.currentTarget.value)+1;
}
private function startGameF(e:TouchEvent):void{
// Dispatched to introView in Main
dispatchEvent(new Event("startGameE"));
}
private function orientationChangeF(e):void{
// Adjust tf’s width when there’s a device rotation.
tf.width = stage.fullScreenWidth-20;
345
346
Chapter 9 n Developing and Distributing Games for Android Devices
// Adjust slider’s y
slider.y = stage.fullScreenHeight-slider.height;
// Have controller make changes to the switches and
// light and wires display. In addition, there’s a
// TF instance added by controller when a GameView
// instance is displayed. And it needs to be
// adjusted when there’s a device rotation.
// It would have been more natural to add the
// TF instance in GameView, but because tapping
// a switch requires an update to the TF instance
// text, I decided it was easier to add the TF
// instance in controller rather than dispatch
// events from controller to GameView.
controller.orientationChangeF(tf.y+tf.height);
}
private function cleanUpF(e:Event):void{
// Remove the listeners that are no longer needed
// and return all the display objects to their pools.
light.removeEventListener(TouchEvent.TOUCH_TAP,startGameF,false);
stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGE,
orientationChangeF,false);
// return before removing or else goto is funky with
// the currentFrame reporting what’s expected but not
// the same as displayed.
for(i=switchNum-1;i>=0;i––){
Switch_Pool.returnF(switchA[i]);
removeChild(switchA[i]);
}
removeChild(slider);
slider.removeEventListener(TouchEvent.TOUCH_BEGIN,explanationF,false);
slider.removeEventListener(TouchEvent.TOUCH_END,explanationF,false);
slider.removeEventListener(TouchEvent.TOUCH_MOVE,sliderChangeF,false);
Slider_Pool.returnF(slider);
Light_Pool.returnF(light);
removeChild(light);
TF_Pool.returnF(TF(tf));
removeChild(tf);
controller.cleanUpF();
}
}
}
Switcher for Android
GameView
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.StageOrientationEvent;
import flash.utils.getTimer;
public class GameView extends MovieClip {
private var switchNum:int;
private var switchA:Vector.<Switch>;
private var light:Light;
private var controller:Controller;
private var i:int;
private var tf:TF;
public function GameView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanUpF,false,0,
true);
}
private function init(e:Event):void{
switchNum = Data.switchNum;
switchA = new Vector.<Switch>(switchNum);
stage.addEventListener(StageOrientationEvent.ORIENTATION_
CHANGE, orientationChangeF,false,0,true);
// Retrieve the needed switches.
switchesF();
// Retrieve the Light instance.
lightF();
// Access the singleton Controller instance.
controllerF();
// Retrieve a textfield used to give user feedback
tfF();
}
private function switchesF():void{
for(i=switchNum-1;i>=0;i––){
switchA[i] = Switch_Pool.retrieveF();
addChild(switchA[i]);
// Layout done in controller
}
}
private function lightF():void{
light = Light_Pool.retrieveF();
// Listen for tfUpdateE dispatched from controller
347
348
Chapter 9 n Developing and Distributing Games for Android Devices
// when a switch is tapped.
light.addEventListener("tfUpdateE",tfUpdateF,false,0,true);
addChild(light);
// Layout done in controller
}
private function controllerF():void{
// Access the Controller instance and listen
// for a gameOverE event.
controller = Controller.getInstance();
controller.addEventListener("gameOverE",gameOverF,false,0,true);
// Call controller’s public init() function passing
// a 4th parameter indicating controller is
// initialized from the GameView instance and needs
// tf updates.
controller.init(switchA,light,0,"gameView");
}
private function tfF():void{
tf = TF_Pool.retrieveF();
addChild(tf);
tfUpdateF();
tf.x = 10;
// light.x is not ready for one frame tick.
tf.addEventListener(Event.ENTER_FRAME,oneTickF,false,0,true);
}
function oneTickF(e:Event):void{
tf.width = light.x-30;
tf.y = stage.fullScreenHeight-tf.height;
tf.removeEventListener(Event.ENTER_FRAME,oneTickF,false);
}
private function tfUpdateF(e:Event=null):void{
// Data.tapNum is updated in controller, tfUpdateE
// is dispatched in controller, and tfUpdateF() is
// called here displaying the updated Data.tapNum
// to the user.
tf.text = "This "+switchNum+"-switch puzzle can be solved with
"+Data.tapMin+" or less taps. So far, you have made "+Data.tapNum+" taps.\n\nTap the
light to restart this puzzle. Double tap the light to quit this puzzle.";
}
private function gameOverF(e:Event):void{
// No need to create a new event. Just dispatch the
// same gameOverE to listener in Main.
dispatchEvent(e);
}
private function orientationChangeF(e):void{
Switcher for Android
// Change layout of switches, light, and wires
// in controller
controller.orientationChangeF(0);
// Change tf layout here
tf.width = light.x-30;
tf.y = stage.fullScreenHeight-tf.height;
}
private function cleanUpF(e:Event):void{
// The usual cleanup, returning objects to their
// pools and removing added event listeners
controller.removeEventListener("gameOverE",gameOverF,false);
stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGE,
orientationChangeF);
for(i=switchNum-1;i>=0;i––){
Switch_Pool.returnF(switchA[i]);
removeChild(switchA[i]);
}
Light_Pool.returnF(light);
removeChild(light);
TF_Pool.returnF(tf);
removeChild(tf);
controller.cleanUpF();
}
}
}
Controller
package com.kglad{
import flash.display.Stage;
import flash.display.Sprite;
import flash.display.MovieClip;
import flash.events.TouchEvent;
import flash.utils.Timer;
import flash.utils.getTimer;
import flash.events.TimerEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
public class Controller extends EventDispatcher{
private static var controllerInstance:Controller;
private static var allowInstantiation:Boolean;
private var i:int;
private var j:int;
private var initX:int = 10;
private var initY:int;
349
350
Chapter 9 n Developing and Distributing Games for Android Devices
private var nextX:int;
private var nextY:int;
private var switchW:int;
private var switchH:int;
private var switchGapX:int = 20;
private var switchGapY:int;
private var switchA:Vector.<Switch>;
private var switchStateA:Vector.<int>;
private var switchA_len:int;
private var light:Light;
private var stageW:int;
private var stageH:int;
private var graphicSP:Sprite;
private var tapIndex:int;
private var viewS:String;
private var closedCircuitBool:Boolean;
private var gameOverTimer:Timer;
private var rNum:int;
private var tapMin:int;
private var prevLightTap:int;
private var tfReady:Boolean;
private static var _stage:Stage;
// This is a singleton class. It has no display representation
// and is not added to the display list. But I needed a
// reference so I could use the drawing methods of the Graphics
// class to create the wires, so I needed to create and add
// a display object, the Sprite graphicSP, to the display
// list, too.
public static function getInstance(stageVar:Stage=null):Controller {
if (controllerInstance == null) {
allowInstantiation=true;
controllerInstance = new Controller();
allowInstantiation=false;
}
if(stageVar){
// My stage reference passed from Main.
_stage = stageVar;
}
return controllerInstance;
}
public function Controller():void {
if (! allowInstantiation) {
Switcher for Android
throw new Error("Error: Instantiation failed: Use
SingletonDemo.getInstance() instead of new.");
}
}
public function
init(_switchA:Vector.<Switch>,_light:Light,_nextY:int,_viewS:String=""):void{
// Used to determine if all the switches are closed.
closedCircuitBool = false;
// Used to prevent communication with tf in
// GameView instance until tf is ready.
tfReady = false;
// init() is called from IntroView and GameView
// instances. When called from the GameView instance,
// the _viewS parameter is passed and used to indicate
// the controller is for the GameView instance.
viewS = _viewS;
// Get a Controller instance reference to _switchA
switchA = _switchA;
// Variables used to position the switches and
// draw the wires.
switchW = switchA[0].width;
switchH = switchA[0].height+10;
// For optimizing
switchA_len = switchA.length;
// Used if the initial state of the switches is
// needed (if and when resetting).
switchStateA = new Vector.<int>(switchA_len);
// Get a Controller instance reference to _light
light = _light;
// Used to position the switches
switchGapY = switchH+30;
// The sprite used for the wires/drawing
if(!graphicSP){
graphicSP = new Sprite();
}
_stage.addChild(graphicSP);
// Instead of attaching listeners to each switch, I
// check for switch tap in stageTapF().
_stage.addEventListener(TouchEvent.TOUCH_TAP,stageTapF,false,0,true);
// Used when GameView instance uses controller.
// Only one gameOverTimer created and never cleared
// from memory while this game exists.
if(!gameOverTimer){
gameOverTimer = new Timer(3000,1);
351
352
Chapter 9 n Developing and Distributing Games for Android Devices
} else {
gameOverTimer.reset();
}
gameOverTimer.addEventListener(TimerEvent.TIMER,gameOverF,false,0,true);
// y property of the first row of switches.
initY = _nextY+4*switchGapY/5;
// Randomize the initial state of the switches
randomizeSwitchesF();
// Initialize the number of switch taps
Data.tapNum = 0;
// Lay out the switches, the light, and the wires
layoutF();
if(viewS=="gameView"){
// GameView instance’s tf is ready. tfReady used
// in updateGameViewTF() so no updates to tf are
// dispatched while randomizing switches and
// before light.x is updated.
tfReady = true;
light.addEventListener(TouchEvent.TOUCH_TAP,lightTapF,false,0,true);
}
}
private function updateGameViewTF():void{
if(tfReady){
light.dispatchEvent(new Event("tfUpdateE"));
}
}
private function lightTapF(e:TouchEvent):void{
// If double tap, exit game.
if(getTimer()-prevLightTap<1000){
// Data.tapNum is used in GameOverView instance
// to display user feedback
Data.tapNum = -1;
// Dispatched to controller in GameView instance,
// which then dispatches the event to Main.
dispatchEvent(new Event("gameOverE"));
} else {
// Otherwise, reset the switches, update Data.tapNum,
// and update tf in GameView.
resetF();
Data.tapNum = 0;
updateGameViewTF();
}
// Used to detect double tap above.
prevLightTap = getTimer();
Switcher for Android
}
private function resetF():void{
// Reset the switches by first "closing" them all...
closeAllSwitchesF();
// ...and then duplicating the initial randomizing taps.
// (See randomizeSwitchesF below.) i needs to be local
// to resetF() because it is changed in checkCircuitF(),
// which is called from stageTapF(), the
// TouchEvent.TOUCH_TAP listener function.
for(var i:int=switchA_len-1;i>=0;i––){
for(var j:int=switchStateA[i]-1;j>=0;j––){
switchA[i].dispatchEvent(new
TouchEvent(TouchEvent.TOUCH_TAP));
}
}
}
private function closeAllSwitchesF():void{
for(i=switchA_len-1;i>=0;i––){
if(switchA[i].currentFrameLabel!="closed"){
switchA[i].gotoAndStop("closed");
}
}
}
private function randomizeSwitchesF():void{
// Initialize a counter to track how many "taps" it
// would take to solve the puzzle by "brute force,"
// i.e., by reversing the taps on each switch.
// You can always reverse the taps: If a switch is
// not tapped, it needs no reversing. If a switch is
// tapped once, tapping twice more will undo its
// initial tap. If a switch is tapped twice, tapping
// once more will undo the initial two taps. tapMin
// tracks the number of reversing taps and provides
// an upper limit to the number of taps needed to
// solve the puzzle.
tapMin = 0;
// Generate the random taps for each switch. I need to
// make i local to this function because it is changed
// in checkCircuitF(), which is called from stageTapF().
for(var i:int=switchA_len-1;i>=0;i––){
// rNum is random int 0,1, or 2, used to
// determine how many taps switchA[i]
// should emulate
rNum = int(3*Math.random());
353
354
Chapter 9 n Developing and Distributing Games for Android Devices
// switchStatA is used if resetting to the
// original puzzle is desired.
switchStateA[i] = rNum;
// tapMin incremented. If rNum=0, no reversing
// taps are needed explaining the modulo 3.
tapMin += (3-rNum)%3;
// Dispatch the events changing the switch
// states.
for(j=rNum-1;j>=0;j––){
switchA[i].dispatchEvent(new
TouchEvent(TouchEvent.TOUCH_TAP));
}
}
// Ensure at least one of the switches is
// not closed.
closedCircuitBool = true;
for(i=switchA_len-1;i>=0;i––){
if(switchA[i].currentFrameLabel!="closed"){
closedCircuitBool = false;
break;
}
}
// If closedCircuitBool is still true, all the switches
// are closed (and the light is on) -> re-randomize
// the switches.
if(closedCircuitBool){
// Re-randomize the switches.
randomizeSwitchesF();
// And turn off the light!
light.gotoAndStop("off");
}
// Data.tapMin is used in the GameView and
// GameOverView instances for user feedback.
Data.tapMin = tapMin;
}
private function layoutF():void{
layoutSwitchesF();
layoutLightF();
layoutWiresF();
}
private function layoutSwitchesF():void{
// Use the display dimensions to lay out
// the display.
stageW = _stage.fullScreenWidth;
Switcher for Android
stageH = _stage.fullScreenHeight;
// The x,y for the top-leftmost switch
nextX = initX;
nextY = initY;
// This is one of two places where I found it easier
// to iterate through switchA from beginning to end
// rather than from end to beginning.
for(i=0;i<switchA_len;i++){
switchA[i].x = nextX;
switchA[i].y = nextY;
// nextX = nextX+switchA[i] + switchGapX and
// must be < stageW - switchA[i+1].width so
// the next switch fits on-stage
// That only matters if there is a next switch,
// so only check that if i is not the last switch
if(i<switchA_len-1){
// Check that the next switch will fit
// on-stage if placed to the right of
// the previous switch.
if(nextX+switchGapX+2*switchW<stageW){
nextX += switchGapX+switchW;
} else {
// If it won’t fit, place the
// next switch on the next y,
// initial x.
nextX = initX;
nextY += switchGapY;
}
}
}
}
private function layoutLightF():void{
// Position the light at the lower right of
// the display.
light.x = stageW - light.width-10;
light.y = stageH - 10;
}
private function layoutWiresF():void{
// Finally, draw the wires to connect the
// switches and light.
with(graphicSP.graphics){
// Clear anything previously drawn.
clear();
355
356
Chapter 9 n Developing and Distributing Games for Android Devices
// Define a lineStyle
lineStyle(0,0x000000);
// Start the line at the left of the display
// (x=0), y equal the first switches y. Notice
// the switch and light have registration
// points where I want wires to "enter."
moveTo(0,switchA[0].y);
// Draw to the first switch.
lineTo(switchA[0].x,switchA[0].y);
// This is the second place I found it
// easier to iterate from beginning to end.
for(i=0;i<switchA_len;i++){
// Move to the right of the switch
// where the wire will "exit."
moveTo(switchA[i].x+switchW,switchA[i].y);
// If there is a next switch, connect to it.
// Otherwise, connect to the light.
if(i<switchA_len-1){
// If the next switch has the same y,
// connect directly to that switch.
if(switchA[i+1].y==switchA[i].y){
lineTo(switchA[i+1].x,switchA[i
+1].y);
} else {
// Otherwise, I have a
// right-to-left connection
// between the two switches.
RtoL(switchA[i],switchA[i+1]);
}
} else {
// Connect to light. If the light is
// to the right of the wire’s exit:
if(switchA[i].x+switchW<light.x){
// Draw a horizontal wire just
// to the left of the light
lineTo(light.x-initX/2,switchA[i].y);
// Draw a vertical wire to
// the light’s y (line down
// to light’s y).
lineTo(light.x-initX/2,light.y);
// Connect to the light (line
// right to the light).
lineTo(light.x,light.y);
Switcher for Android
} else {
// The light is to the left
// of the switch. Use a
// right-to-left connection.
RtoL(switchA[i],light);
}
}
}
}
}
private function RtoL(s1:Switch,obj2:MovieClip):void{
with(graphicSP.graphics){
// line right just distal to s1 switch
lineTo(s1.x+switchW+initX/2,s1.y);
// line down between s1 and obj2
lineTo(s1.x+switchW+initX/2,s1.y+switchH/4);
// line left just proximal to obj2
lineTo(obj2.x-initX/2,s1.y+switchH/4);
// line down to obj2
lineTo(obj2.x-initX/2,obj2.y);
// line right to obj2
lineTo(obj2.x,obj2.y);
}
}
private function stageTapF(e:TouchEvent):void{
// The stage was tapped. Check if the TouchEvent
// target is a switch and, if so, which one.
tapIndex = switchA.indexOf(e.target);
// if tapIndex>-1, a switch was tapped.
if(tapIndex>-1){
// The Switch class has a public nextF()
// that changes the switch state.
e.target.nextF();
// Increment Data.tapNum
Data.tapNum++;
// If this controller is being used by the
// GameView instance, update tf to show the
// user another switch tap has been detected
// and display Data.tapNum (among other
// things).
if(viewS=="gameView"){
updateGameViewTF();
}
// If this is not the last switch, change the
357
358
Chapter 9 n Developing and Distributing Games for Android Devices
// state of the next (in switchA) switch.
if(tapIndex+1<switchA_len){
switchA[tapIndex+1].nextF();
}
// If this is not the first switch, change the
// state of the previous (in switchA) switch.
if(tapIndex-1>=0){
switchA[tapIndex-1].nextF();
}
// Check if circuit closed and puzzle solved.
checkCircuitF();
}
}
private function checkCircuitF():void{
// Initialize closedCircuitBool
closedCircuitBool = true;
// Loop through the switches looking for one that
// is not "closed."
for(i=switchA_len-1;i>=0;i––){
if(switchA[i].currentFrameLabel!="closed"){
closedCircuitBool = false;
break;
}
}
if(closedCircuitBool){
// Circuit is closed. Puzzle is solved.
closedCircuitF();
}
}
private function closedCircuitF():void{
// Display light turned on.
light.gotoAndPlay("on");
if(viewS=="gameView"){
// Exit the game after displaying the light
// "on" for a few seconds.
gameOverTimer.start();
}
}
private function gameOverF(e:TimerEvent):void{
// Dispatch "gameOverE" event to GameView instance,
// which then dispatches the event to Main.
dispatchEvent(new Event("gameOverE"));
}
public function orientationChangeF(_nextY:int):void{
Switcher for Android
// Adjust the layout using the current screen width
// and height (defined in layoutSwitchesF).
layoutF();
}
public function cleanUpF():void{
// Nothing needs to be returned to a pool here.
// The only object created is graphics, and that will
// be reused each time the only Controller instance’s
// init() function is called. It only needs to be
// removed from the display.
_stage.removeChild(graphicSP);
// Remove all the listeners created here.
gameOverTimer.removeEventListener(TimerEvent.TIMER,gameOverF,
false);
if(viewS=="gameView"){
light.removeEventListener(TouchEvent.TOUCH_TAP,lightTapF,false);
}
_stage.removeEventListener(TouchEvent.TOUCH_TAP,stageTapF,false);
}
}
}
GameOverView
// Nothing new here
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.StageOrientationEvent;
import flash.display.Sprite;
public class GameOverView extends MovieClip {
private var tf:TF;
private var replay_btn:Replay_Btn;
public function GameOverView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanUpF,false,0,
true);
}
private function init(e:Event):void{
stage.addEventListener(StageOrientationEvent.ORIENTATION_
CHANGE, orientationChangeF,false,0,true);
tfF();
replayButtonF();
359
360
Chapter 9 n Developing and Distributing Games for Android Devices
}
private function replayButtonF():void{
replay_btn = Replay_Btn_Pool.retrieveF();
addChild(replay_btn);
replay_btn.x = (stage.fullScreenWidth-replay_btn.width)/2;
replay_btn.y = tf.y+tf.height+100;
replay_btn.addEventListener("replayE",replayF,false,0,true);
}
private function replayF(e:Event):void{
dispatchEvent(e);
}
private function tfF():void{
tf = TF_Pool.retrieveF();
addChild(tf);
if(Data.tapNum<0){
tf.text = "You failed to solve the previous
"+Data.switchNum+"-switch puzzle.\n\nTap the button below to replay.";
} else {
tf.text = "You solved the previous "+Data.switchNumpdf
+"-switch puzzle using "+Data.tapNum+" taps. This puzzle could be solved using
"+Data.tapMin+" taps or less.\n\nYou scored
"+Math.min(100,int(100*Data.tapMin/Data.tapNum))+"%.\n\nTap the button below to
replay.";
}
tf.width = stage.fullScreenWidth-20;
tf.y = 100;
}
private function orientationChangeF(e):void{
tf.y = 100;
tf.x = 10;
tf.width = stage.fullScreenWidth-20;
replay_btn.x = (stage.fullScreenWidth-replay_btn.width)>>1;
replay_btn.y = tf.y+tf.height+100;
}
private function cleanUpF(e:Event):void{
stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGE,
orientationChangeF,false);
TF_Pool.returnF(tf);
removeChild(tf);
replay_btn.removeEventListener("replayE",replayF,false);
Replay_Btn_Pool.returnF(replay_btn);
removeChild(replay_btn);
}
}
}
Switcher for Android
Data
package com.kglad {
public class Data {
// variables with default values below
private static var _switchNum:int;
private static var _sliderMin:int
private static var _sliderMax:int;
private static var _tapNum:int;
private static var _tapMin:int;
public function Data() {
// constructor code
}
public static function init():void{
// slider varies from initial _switchNum to
// _switchMax. The number of switches is
// 2*slider.value+1;
_sliderMin = 2;
_sliderMax = 19;
_switchNum = 5;
_tapNum = 0;
}
public static function set switchNum(n:Number):void{
_switchNum = n;
}
public static function get switchNum():Number{
return _switchNum;
}
public static function get sliderMin():Number{
return _sliderMin;
}
public static function get sliderMax():Number{
return _sliderMax;
}
public static function set tapNum(n:Number):void{
_tapNum = n;
}
public static function get tapNum():Number{
return _tapNum;
}
361
362
Chapter 9 n Developing and Distributing Games for Android Devices
public static function set tapMin(n:Number):void{
_tapMin = n;
}
public static function get tapMin():Number{
return _tapMin;
}
}
}
Switch
package com.kglad {
import flash.display.MovieClip;
public class Switch extends MovieClip {
public function Switch() {
}
// This public function is used to update the
// switch state.
public function nextF():void{
if(this.currentFrame<3){
this.nextFrame();
} else {
this.gotoAndStop("open");
}
}
}
}
TF
package com.kglad {
import flash.text.TextField;
import flash.text.TextFormat;
import flash.events.Event;
public class TF extends TextField {
private var tfor:TextFormat;
public function TF() {
tfor = new TextFormat();
var verdana:VerdanaReg = new VerdanaReg();
tfor.font = verdana.fontName;
tfor.size = 12;
tfor.leading = -1;
this.embedFonts = true;
// Again, use defaultTextFormat when assigning a
Switcher for Android
// TextFormat instance before text is assigned.
// After text is assigned, use the
// setTextFormat() method.
this.defaultTextFormat = tfor;
this.multiline = true;
this.wordWrap = true;
this.autoSize = "left";
this.border = true;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
this.width = stage.fullScreenWidth-20;
}
}
}
Replay_Btn
package com.kglad {
import flash.display.Sprite;
import flash.events.TouchEvent;
import flash.events.Event;
public class Replay_Btn extends Sprite{
public function Replay_Btn() {
this.addEventListener(TouchEvent.TOUCH_TAP,tapF,false,0,true);
with(this.graphics){
beginFill(0xE08000);
drawRect(0,0,50,50);
endFill();
}
}
private function tapF(e:TouchEvent):void{
dispatchEvent(new Event("replayE"));
}
}
}
Slider
package com.kglad {
// There is no change in Slider
import flash.display.MovieClip;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.TouchEvent;
import flash.events.Event;
363
364
Chapter 9 n Developing and Distributing Games for Android Devices
import flash.geom.Rectangle;
public class Slider extends MovieClip {
private var leftX;
private var rightX;
private var _value:Number;
private var _max:Number;
private var _min:Number;
public function Slider() {
// This is a horizontal slider, so define the left
// and right extremes of the thumb (which has a
// center x registration point).
// When the slider thumb.x = leftX, _value should
// be _min. When the slider thumb.x = rightX, _value
// should be _max. Linear interpolation is used to
// determine _value when thumb.x is between
// leftX and rightX.
leftX = this.track_mc.getRect(this).x;
rightX = this.track_mc.getRect(this).width;
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
// Two other touch events defined exactly like you
// would expect
this.thumb.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginF);
this.thumb.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveF);
this.thumb.addEventListener(TouchEvent.TOUCH_END, touchEndF);
}
private function touchBeginF(e:TouchEvent):void{
// TouchEvents have a touchPointID, which is
// necessary to keep track of different
// simultaneous touch points when
// Multitouch.inputMode = MultitouchInputMode.GESTURE.
// With single touch points like in this game,
// keeping track of touch points is not critical,
// but it is required for the startTouchDrag()
// and stopTouchDrag() methods. The other
// parameters in startTouchDrag() are the same
// as startDrag().
e.currentTarget.startTouchDrag(e.touchPointID, false, new Rectangle
(0,0,this.rightX,0));
}
Switcher for Android
private function touchMoveF(e:TouchEvent):void{
// Update _value
valueF();
}
private function touchEndF(e:TouchEvent):void{
valueF();
e.currentTarget.stopTouchDrag(e.touchPointID);
}
private function valueF():void{
// Linear interpolation to find _value given thumb.x
_value = (_min*rightX-_max*leftX+(_max_min)*this.thumb.x)/(rightX-leftX);
}
public function get value():Number{
return _value;
}
public function set value(n:Number):void{
_value = n;
// Another use of linear interpolation to find
// thumb.x given _value
this.thumb.x = (_value*(rightX-leftX)-_min*rightX_max*leftX)/(_max-_min);
}
public function set max(n:Number):void{
_max = n;
}
public function get max():Number{
return _max;
}
public function set min(n:Number):void{
_min = n;
}
public function get min():Number{
return _min;
}
}
}
// All but three of the pool classes are exactly the same except
// for data types. The three pool classes with a little difference
// are the following, which encode some housekeeping in returnF()
365
366
Chapter 9 n Developing and Distributing Games for Android Devices
Switch_Pool
package com.kglad{
public class Switch_Pool {
private static var pool:Vector.<Switch>;
public static function init(poolSize:int):void {
pool = new Vector.<Switch>(poolSize);
for(var i:int=poolSize-1;i>=0;i––){
pool[i] = new Switch();
}
}
public static function retrieveF():Switch {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new Switch();
}
}
public static function returnF(sw:Switch):void {
if(sw.currentFrameLabel!="closed"){
sw.gotoAndStop("closed");
}
pool.push(sw);
}
}
}
Light_Pool
package com.kglad{
public class Light_Pool {
private static var pool:Vector.<Light>;
public static function init(poolSize:int):void {
pool = new Vector.<Light>(poolSize);
for(var i:int=poolSize-1;i>=0;i––){
pool[i] = new Light();
}
}
public static function retrieveF():Light {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
return new Light();
Testing an Android Game
}
}
public static function returnF(light:Light):void {
if(light.currentFrameLabel!="off"){
light.gotoAndStop("off");
}
pool.push(light);
}
}
}
TF_Pool
package com.kglad{
public class TF_Pool {
private static var pool:Vector.<TF>;
public static function init(poolSize:int):void {
pool = new Vector.<TF>(poolSize);
for(var i:int=poolSize-1;i>=0;i––){
pool[i] = new TF();
}
}
public static function retrieveF():TF {
if (pool.length>0) {
return pool.pop();
} else {
// this branch should not execute.
trace("should not execute in TF_Pool");
return new TF();
}
}
public static function returnF(tf:TF):void {
tf.text = "";
pool.push(tf);
}
}
}
Testing an Android Game
Hopefully you have an Android device you can use for testing. If so, or if you have
Flash CS6 or better, you can skip the sections on Android Emulators, Android Debug
Bridge, and Adobe Air Developer Tool.
367
368
Chapter 9 n Developing and Distributing Games for Android Devices
AIR Debug Launcher
I recommend using the AIR Debug Launcher (if you have Flash Pro CS6 or better) to
test an Android game, coupled with intermittent testing on an Android device. With
Flash Pro CS6 or better, the development and testing processes for Android and iOS
are similar.
Click Control > Test Movie > in AIR Debug Launcher (Mobile) and test your game.
After you resolve coding bugs, test it on an Android device to check performance.
You’ll learn how to publish an .apk file and load that onto an Android device in the
upcoming “AIR for Android Settings: Deployment Tab” section.
For more information about the AIR Debug Launcher, read the “Testing an iOS
Game” section toward the end of Chapter 8, “Developing and Distributing Games
for iOS Devices.” As a reminder, you will need to check the Auto Orientation option
in the Accelerometer menu’s Settings subpanel if you want the AIR display to reflect
vertical and horizontal device orientation as you change the Z-rotation in the Simulator Controller. You open that subpanel by clicking the Settings icon to the right of
the Reset icon (refer to Figure 8.3).
If you have Flash CS5 or Flash CS5.5 and you cannot use the AIR Debug Launcher,
you must use an Android device or an Android emulator for testing. Using an
Android device is preferable because setting up Android emulators is timeconsuming, and their performance is terrible.
However, if you have no other option for testing your Android game, here is how to set
up and use Android emulators. I recommend you read the next three sections—“Android
Emulators,” “Android Debug Bridge (ADB),” and “Adobe AIR Developer Tool (ADT)”—
only if you have no other option for testing and debugging your Android game.
Android Emulators
Whereas there is no emulator for iOS (at this time), there is one for the Android OS.
At the time of this writing, I found it inferior to the mobile simulator that Flash Pro
CS6 offers.
But you might find it better than nothing if you don’t have an Android device to
connect to your computer, and you have Flash Pro CS5 or CS5.5.
Broadly speaking, to set up emulators:
1. Download and install the Android SDK.
2. Create one or more Android OS emulators.
3. Load Air into your emulators.
4. Load your Flash game into your emulators.
Testing an Android Game
If that sounds like a hassle, you have a misimpression. It is much more than a hassle
because you have to use the command-line interpreter to load Air and your game
into your emulators.
If you’ve never used a command-line interpreter, you have an opportunity to see
what home computing was like in the dark ages before Apple created a computer
with a graphics interface that Microsoft quickly copied, bringing home computing
to the masses. And, if you have used a command-line interpreter in the past, this
should give you a reminder of why you almost certainly avoided it once you discovered the ease of a graphics interface.
I will sketch the steps needed to install and prepare emulators for Android, but if you
can get your hands on an Android device or you have Flash Pro CS6, don’t bother. If
you have CS6, use its simulator. If you have an Android device, use it. And ideally,
you’ll have both, so you can use both for testing.
To start, download the Android SDK: http://developer.android.com/sdk/index.html
(refer to Figure 9.2). This is only part of the download. Windows users should download the .exe so it can check for the needed Java SE Development Kit.
Download
for windows
Download
for mac
Figure 9.2
Source: Google® Corporation.
369
370
Chapter 9 n Developing and Distributing Games for Android Devices
That first download will extract and install the basic tools used to download the rest of the
SDK. So, after installing it, navigate to your install directory and run SDK Manager.exe.
You will be offered an assortment of packages to download and install.
Check the Tools package, the Android OS versions you want to emulate, and the
extras you want to access. There is no reason to select any OS versions less than 2.2
if you’re only developing Android apps using Flash, because version 2.2 was the first
to support Adobe Air.
You can delete installed packages, and you can download and install more packages
at any time by re-running SDK Manager.exe. When you’re ready, click the Install
Package button. It may take some time to complete the downloads and installation,
so take a break or work on something else while you’re waiting.
When that is complete, click AVD Manager.exe (Android Virtual Device Manager) if
you want to create an emulator (or emulators). You’ll probably want to create a desktop shortcut to the AVD Manager because you’ll use this every time you want to start
an emulator. You might as well do that now.
Click a target OS to emulate from the combobox and then create an emulator name
that makes it easy to remember which OS you’re emulating. There are a number of
name restrictions, so if the Create AVD button isn’t clickable, check just above the
button for error specifics. Select an SD card size (1024 MB works) and click Create
AVD unless you want to change one of the other default settings.
Your emulators should be listed in the AVD Manager, where you can select one and
click Start. A Launch Options panel should open, allowing you to launch your emulator. Your emulator should finally start and be ready for test clicks after a short
delay. You can click your Home button to see how awful the emulator’s performance
is, at least on Windows.
If you aren’t discouraged yet and you still want to proceed, you must install Adobe
AIR on each of your emulators. To do that, you must download the Adobe AIR runtime application for Android, which is part of the Adobe AIR SDK (www.adobe.com/
devnet/air/air-sdk-download.html).
Download and extract those files. The file you want to install on your emulators
(Runtime.apk) is in the AdobeAIRSDK/runtimes/air/android/emulator directory.
You are now ready to install the Air runtime on your emulators and then install your
games. You have two ways to install Air and games onto your emulators. You can
use the Android Debug Bridge (ADB) or the Adobe AIR Developer Tool (ADT).
Testing an Android Game
Note
Android has an Eclipse plug-in called Android Development Tools (usually denoted by ADT), which I
won’t mention again. Just be aware when you’re reading elsewhere that ADT may not refer to the AIR
Developer Tool.
Both ADB and ADT are command-line tools, and they are equally aggravating to use.
All command-line tools, including those two, must be executed from the commandline interpreter.
If you’re using Windows, you can open the command-line interpreter by entering
cmd in your OS run panel (press the Windows key + R). If that window is small
(and especially if it’s less than 800px wide), click the command panel’s icon at panel’s
upper left, click Properties, and adjust the settings so you see a decent-sized interpreter panel in the preview. I couldn’t make sense of the width/height numbers, but
the preview was helpful.
If you’re using a Mac, open Applications/Utilities/Terminal. Resizing the panel is
obvious on a Mac.
Type help at the command-line interpreter prompt. You will see a list of available
commands. For help using a specific command, type help followed by the command.
The command-line interpreter is not case-sensitive.
Android Debug Bridge (ADB)
Now, you can either navigate (in the command-line interpreter) to ADB or add it to
your system path. If you’re going to use ADB more than a few times, you should add
it to your system path.
To understand why you should add it to your system path, check out the command
required to execute ADB and have it add the AIR runtime to my emulator-5556:
C:\Program Files (x86)\Android\android-sdk\platform-tools\ADB -s emulator-5556 install
F:\Downloads\AdobeAir\AdobeAIRSDK\runtimes\air\android\emulator\Runtime.apk
Your command will be different. However, it won’t be much shorter, if at all.
You can also use local paths to one or both files, so by navigating to one of the two
(ADB.exe or Runtime.apk) directories, you can significantly abbreviate that command. But, it is still tediously long and easy to make typos that are time-consuming
to fix.
To add the ADB path to your system path in Windows, click Start and then rightclick My Computer. Click Properties > Advanced System Settings > Environment
371
372
Chapter 9 n Developing and Distributing Games for Android Devices
Variables. In the System Variables field, click Path > Edit and then click the arrow to
the last entry of the Variable value line. Append a semicolon and type the full path to
your platform-tools directory. Finally, click OK.
You can now type ADB from anywhere in your command-line editor, and ADB in
the platform-tools directory will execute. So, to install the Adobe AIR runtime on one
of your emulators, start an emulator (using AVD Manager). After it has completed
its load sequence, you can find your device name(s) by typing ADB devices at the
command-line prompt. You should see something like emulator-xyzw.
You can now navigate to the directory where you extracted the Adobe AIR SDK
(AdobeAIRSDK\runtimes\air\android\emulator\Runtime.apk) and type:
ADB -s emulator-xyzw install Runtime.apk
where emulator-xyzw is your device name, found using the Devices command. (You
can also find this in your taskbar if you’re using Windows.)
To install a Flash game on your Android emulator, navigate to the directory with the
game and use the same syntax, substituting yourgame.apk for Runtime.apk:
ADB -s emulator-xyzw install yourgame.apk
To delete an app from your emulator or stop it from running, click the Home button
and click the Apps icon. Click the Menu button and click Manage Apps. Navigate to
the app you want to delete or stop and click Uninstall or Force Stop.
You can edit your system path in a Mac by editing the .profile file in your home
directory. If there are already entries, append the additional path by separating with
a full colon, not a semicolon.
For additional information about ADB, check
http://developer.android.com/guide/developing/tools/ADB.html.
Adobe AIR Developer Tool (ADT)
The command required to execute ADT and have it add the AIR runtime to my
emulator-5556 is:
F:Downloads\AdobeAir\AdobeAIRSDK\bin\adt -installRuntime -platform android -device
emulator-5556 -package F:Downloads\AdobeAir\AdobeAIRSDK\runtimes\air\android\emulator\
Runtime.apk
That is clearly a ridiculous amount of typing with far too many opportunities for
typos. If you navigate to your AdobeAIRSDK directory in the command-line interpreter, you can use shorter local paths:
Testing an Android Game
\bin\adt -installRuntime -platform android -device emulator-5556 -package \runtimes
\air\android\emulator\Runtime.apk
Your local paths will look exactly the same, though the location of your AdobeAIRSDK directory will be different from mine. But it’s still too much tedious typing, especially because you have to navigate to the AdobeAIRSDK directory before
you can use that shorter command.
So, the benefit of adding the path to adt.bat is significant if you’re going to use ADT
to install the runtime and games onto Android emulators.
To add the ADT path to your system path in Windows, click Start and right-click
My Computer. Click Properties > Advanced System Settings > Environment Variables. In the System Variables field, click Path > Edit. Click the arrow to the last
entry of the Variable value line. Append a semicolon and type the full path to your
AdobeAir\AdobeAIRSDK\bin directory. Finally, click OK.
You can now type adt from anywhere in your command-line editor, and ADT in the
AdobeAIRSDK\bin directory will execute. So, to install the Adobe AIR runtime on
one of your emulators, start an emulator (using AVD Manager). After it has completed its load sequence, you can find your device name(s) by executing:
adt devices
at the command-line prompt from the C:\Program Files (x86)\Android\android-sdk\
platform-tools\directory or from anywhere if you add C:\Program Files (x86)
\Android\android-sdk\platform-tools\ to your system path.
You can now navigate to the directory where you extracted the Adobe AIR SDK
(AdobeAIRSDK\runtimes\air\android\emulator\Runtime.apk) and type:
adt -installRuntime -platform android -device emulator-xyzw -package Runtime.apk
where emulator-xyzw is your device name, found using the Devices command. (You
can also find it in your taskbar if you are using Windows.)
To install a Flash game on your Android emulator, navigate to the directory with the
.apk game file and use:
adt -installApp -platform android -device emulator-xyzw -package yourgame.apk
where emulator-xyzw is your device name (for example, emulator-5554), found using
the Devices command (you can also find it in your taskbar if you’re using Windows)
and yourgame.apk is the file name of your Android game.
373
374
Chapter 9 n Developing and Distributing Games for Android Devices
To delete an app from your emulator or stop it from running, click the Home button
and then the Apps icon. Click the Menu button and then click Manage Apps. Navigate to the app you want to delete or stop and then click Uninstall or Force Stop.
You can edit your system path in a Mac by editing the .profile file in your home
directory. If there are already entries, append the additional path by separating with
a full colon.
For additional information about ADT, check
http://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade467fd9.html.
Publishing Your Game for Android
Publishing for Android is much easier than publishing for iOS. There is no labyrinth
of steps like those mandated by Apple.
Furthermore, the marked contrast between Apple and everyone else extends to
uploading your finished game to Google Play (the current version of Android Market) and the Amazon App Store, for example.
I will provide the links for registration and uploading games in the next “Distributing
Your Game for Android” section. For now, let’s continue with the publishing
dialogue.
AIR for Android Settings: General Tab
Open Flash and create a new FLA or load an FLA (such as switcher5.fla).
Click File > Publish Settings > SWF and select the most recent version of AIR for
Android in the Target combobox. To the right of the Target combobox is the Player
Settings icon. Click it to open the Player Settings panel.
If you selected AIR for Android in the Target combobox, this panel should be the
AIR for Android Settings panel. The General tab should be selected by default. If
not, click it. (See Figure 9.3.)
Publishing Your Game for Android
Figure 9.3
The AIR for Android Settings panel with the General tab selected.
Source: Adobe Systems Incorporated.
The Output File value is the name of the .apk file that Flash will publish. It is the file
that will be installed on the devices and/or emulators for testing.
The App Name field contains the name of your game, which will appear under your
game’s icon when installed on an Android device (and in the app stores). A limited
number of characters will display.
The Version value doesn’t need to change unless you’re updating a previous game
version.
The Aspect ratio should be Auto if you want your game to rotate when the device is
rotated. Otherwise, select Portrait or Landscape.
375
376
Chapter 9 n Developing and Distributing Games for Android Devices
Check Full screen and/or Auto orientation if desired. You should check Auto orientation if you selected Auto in the Aspect ratio combobox.
Render mode is probably the most important option in this panel. What you select
there can make the difference between a poorly performing game and a game with
smooth play. There are some guidelines for what you should select, but none of
them is worthwhile, except that if you’re using Stage3D API, you must select Direct.
Otherwise, you should test with both CPU and GPU to see which is best. Generally,
if you’re doing bitmap manipulation, GPU will be better.
But I don’t think that’s helpful. You won’t care whether performance is usually better
with GPU render mode. You want to know if performance is better with GPU mode.
And that is best determined by testing the performance with both CPU and then
GPU modes on the target (not emulator) platform.
Again, if you are using Stage3D choose Render mode Direct. No other option will
work with Stage3D.
If you are not using Stage3D, choose Render mode CPU and test on your target
Android device. Then choose Render mode GPU and test on your target Android
device. Use the Render mode that worked best when you tested.
There is also an Auto render mode, but I believe that’s still the same as CPU mode.
Eventually, Adobe may implement (or abandon) some sort of Auto render mode, but
as of the time of this writing, Auto and CPU mode are the same.
And lastly, the Included Files section of this panel should contain two files by default:
the published SWF and an XML file called the application descriptor. The application
descriptor contains the information from the AIR for Android Settings panel that
you’re currently editing.
In addition, you can (and should) add any files needed by your game. None of your
class files needs to be added, because all that code is compiled into your SWF by
Flash when you publish the SWF. But if you load bitmaps, data files, or another
SWF, you should add them to this section. (Another difference between Flash-made
iOS and Android apps/games is you can load SWFs into an Android app/game, and
code in the loaded SWF will execute.)
You can click the icon on the left to add individual files to the included file list, and
you can click the icon on the right to add entire directories of files. The middle icon
is used to remove something you previously added that is no longer needed.
Publishing Your Game for Android
AIR for Android Settings: Deployment Tab
Click the Deployment tab. Here is where you will create your developer Certificate
(See Figure 9.4).
Figure 9.4
AIR for Android Settings panel with Deployment tab selected.
Source: Adobe Systems Incorporated.
To the right of the Certificate field is a Create button that you can click to create a
self-signed digital Certificate (see Figure 9.5).
377
378
Chapter 9 n Developing and Distributing Games for Android Devices
Figure 9.5
Create Self-Signed Digital Certificate panel.
Source: Adobe Systems Incorporated.
Fill in the fields using your name in the first three fields unless you’re part of an
organization, in which case you can fill in your name, your department, and your
organization’s name in the first three fields. Enter and confirm a password that you
will use every time you publish using this Certificate. Leave the encryption type at
1024-RSA (unless you know you need stronger encryption), leave the validity period
at 25 years (or increase it), and save your Certificate using a name that indicates it is
an Android Certificate.
If you already have a Certificate, you need not create another one. Click the Browse
button instead of the Create button and navigate to your Certificate.
If you want or need to create a Certificate issued by a certification authority, be prepared to pay a few hundred dollars per year. You can use VeriSign, Thawte, GlobalSign, or ChosenSecurity.
Cost and exact steps to create and download a verified Certificate vary. You can get
detailed help about using a certification authority from Adobe at
http://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade467ff0.html.
Publishing Your Game for Android
Next, enter the password associated with your Certificate and check Remember Password for this session unless you want to re-enter the password every time you publish during your current session.
Select one of the Android deployment option types.
1. Device release
2. Emulator release
3. Debug
Option 1
Use Option 1 for testing on an Android device. If your Android device is connected
to your development platform when you publish or an Android emulator is running,
Flash will install your game on your device/emulator if you check Install Application
on the Connected Android Device toward the bottom of this panel.
If your Android device is not recognized, you may need to install a USB driver for
Android. Open the Device Manager to see whether there is a driver issue. If there is,
you can manually install a driver from the AirX.Y/install/android/usb_drivers subdirectory of your Flash Pro installation directory.
Option 2
If you have a version of Flash Pro that lets you select Emulator Release and that
allows you to select Embed AIR Runtime with Application, you could use this option
to test in the Android emulator. In my Flash Pro CS6, selecting Emulator Release
prohibits embedding the AIR runtime, which means you cannot use the emulator
release in the Android emulator, and that makes the Emulator Release option
useless.
However, if you can embed the AIR runtime with the Emulator release, you can
install and use this release in the Android emulator. To install the published .apk
file manually (not recommended if an emulator is running at the time you publish
when you can direct Flash to install it), start your emulator (using your shortcut to
the AVD Manager) and then, in the command-line interpreter, navigate to the directory with the .apk game file and use:
adt -installApp -platform android -device emulator-xyzw -package yourgame.apk
where emulator-xyzw is your device name (for example, emulator-5554), found using
the Devices command (and can also be found in your taskbar if you’re using Windows), and yourgame.apk is the file name of your Android game.
379
380
Chapter 9 n Developing and Distributing Games for Android Devices
Your game should install on that emulator. If your emulator’s APPS screen is filled,
you may need to navigate to the bottom right (using your keyboard arrow keys or the
emulator’s arrow keys) and then click the right arrow to see more installed
applications.
Option 3
If you want to test on an Android device and see trace output in Flash, select Option 3.
Hopefully, you will see a network interface for remote debugging displayed in the
combobox. Copy the IP address if that is what you’re going to select. After selecting a
network interface, publish your .apk game file.
Follow the same steps listed in Option 1 to load your game onto your Android device.
(You won’t be able to use an emulator with remote debugging unless you can
embed the AIR runtime.) In Flash, click Debug > Begin Remote Debug Session >
ActionScript 3.0.
Tap your newly loaded game. It may take a minute to start up. When it does, you
should see a Flash Debugger prompt for the IP address or hostname that you just
copied. Type the IP address or hostname and tap OK.
AIR for Android Settings: Icons Tab
Using this tab, you can add icons for your game when it’s installed on your target
device. You can create these icons using Flash (or your preferred graphics program).
To use Flash, add a MovieClip (such as switch), Button, or Graphic to the stage, size it
so the largest dimension (width or height) matches the icon size, right-click it, and
click Export PNG Sequence.
If you used a multiframe object, pick the PNG that you want to use for the icon and
delete the rest. Add all the icon sizes used by your target device.
AIR for Android Settings: Permissions Tab
Check the permissions needed by your game, if there are any. Users installing your
game will see a warning notifying them that your game is seeking access to features
related to privacy or security.
If your game needs a permission and you fail to request it, your game—or at least the
part of your game that needs that permission—will fail. If your game needs no permissions, you will see a warning from Flash when you publish. You can ignore that
warning (See Figure 9.6).
Publishing Your Game for Android
Figure 9.6
Expected Flash warning when no permissions are requested.
Source: Adobe Systems Incorporated.
AIR for Android Settings: Languages Tab
If you’re using Flash Pro CS6 or better, you will have a Languages tab in your AIR
for Android Settings panel. Otherwise, you won’t, and you can ignore this section.
The panel displayed by selecting this tab allows you to indicate other languages your
game supports. That information is added to your application descriptor and will be
displayed by the Google Play Store.
381
382
Chapter 9 n Developing and Distributing Games for Android Devices
It is important to note that no language support is added to your game no matter
what languages you check in this panel. Actually, adding language support is up to
you, the developer.
Distributing Your Game for Android
Here is where you can get some perspective on the difficulty imposed by Apple for
distributing games. For everyone other than Apple, there is essentially nothing to say
about how to distribute your game. Just navigate to the links and follow the
directions.
n
https://play.google.com/apps/publish/signup. This link is to Google Play
(formerly Android Market), where you are charged $25 (which I believe is a
one-time charge, not per year).
n
https://developer.amazon.com/welcome.html. This link is to Amazon App
Store for Android, where you are charged $99 per year (but the first year is
free—at least at the time of this writing).
Of course, if you sell your game or anything in your game, each store will take a cut.
Check their terms if that matters to you.
Chapter 10
3D Game Development
In this chapter, I’ll discuss how to get started with developing a 3D game using Flash.
Thanks to major improvements with graphics handling introduced with Flash
Player 11, there has been an increased interest in Flash 3D games.
The big change with Flash Player 11 was the addition of Stage3D rendering to Flash
games. Stage3D rendering allows you to leverage GPU acceleration that should dramatically improve the performance of games that use it.
At the time of this writing (Flash Player 11.2), there is still room for improvement
with some Flash Players (especially debug versions) in some browsers. Hopefully, by
the time this book is published, the latest Flash Player version will offer a major boost
to Stage3D games across all platforms with both debug and non-debug versions.
The Stage3D APIs offer low-level access to encode highly efficient GPU acceleration.
However, the Stage3D APIs are so complex to use that they aren’t suitable for this
book’s audience.
Fortunately, several available frameworks provide a buffer between the Stage3D APIs
and the developer, allowing the use of higher-level APIs. Among those frameworks is
Starling, a 2D framework encountered in Chapter 7, “Optimizing Game Performance,”
and 3D frameworks Alternativa3D, Away3D, Flare3D, Proscenium, and more.
383
384
Chapter 10 n 3D Game Development
One of the easiest to use is Flare3D (www.flare3d.com). For noncommercial use, it’s
free. However, it is not open source, and it costs $496 per year for commercial use, so
if those are issues for you, you may want to skip the next section.
If you need information about the basics of 3D graphics, check http://computer
.howstuffworks.com/3dgraphics.htm.
If you need background definitions of the terms used in 3D programming, check
http://jmonkeyengine.org/wiki/doku.php/jme3:terminology. Even though that link is
related to a Java programming engine, the terms used are common to all 3D
programming.
Flare3D
To start using Flare3D, click the Download Now button from their main page (www.
flare3d.com). Then click the Download Trial button for the noncommercial version
of Flare3D. After you fill in their form, a link to the download package will be
emailed to the address you supplied in the form.
You will download Flare3D.zip, which, when unzipped, will create some files and five
directories. The lib and docs directories contain the files we will use in this section.
The lib directory contains a .swc file that contains the compiled Flare3D code. You
will need to add that SWC file to your library path.
Click File > Publish Settings > ActionScript Settings icon (see Figure 10.1). Click the
Library Path tab and the Browse to SWC File icon (see Figure 10.2). Finally, navigate
to the Flare3D SWC file and click Open, then click OK, and then OK again.
Flare3D
The ActionScript
Settings icon
Figure 10.1
Click File > Publish Settings to view the Publish Settings panel.
Source: Adobe Systems Incorporated.
385
386
Chapter 10 n 3D Game Development
The Browse
to SWC
file icon
Figure 10.2
After clicking the ActionScript Settings icon, you will see the Advanced ActionScript 3.0 Settings panel.
Source: Adobe Systems Incorporated.
The docs directory contains the Flare3D API. The API is incomplete, but it’s still
helpful. I needed to supplement the information in the API with experimentation
and Google searches.
To open the API, navigate to the docs directory and open index.html in your
browser. You should see a document that is laid out in the familiar format of the
Flash API, with packages listed in an upper-left panel and classes listed in a lowerleft panel.
I’m going to demonstrate some Flare3D basics using a game (Yellow Planet) I downloaded from the Flare3D website. Open/support files/Chapter 10/yp/yp.fla in Flash
Version 01
Pro. You can remove the path to the Flare3D SWC file that I used and add the path
to the Flare3D SWC that you downloaded.
Then, in /support files/Chapter 10/yp/com/kglad, open the class files Main, IntroView,
GameOverView, Data, and pseudo class GameView_01. Rename GameView_01 to GameView.
All of these files except GameView should look very familiar. I’m not going to say anything about any of the classes except GameView, which is the only class that uses the
Flare3D API and is the only class with code that hasn’t been covered previously.
All of the classes other than GameView will remain unchanged through all the game
versions in this section, so they aren’t listed with a version number. GameView contains
extensive comments to help you understand how Flare3D works. You should have
the following….
Version 01
Main
package com.kglad {
import flash.display.Sprite;
import flash.events.Event;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
public class Main extends Sprite {
private var introView:Sprite = new IntroView();
private var gameView:Sprite = new GameView();
private var gameOverView:Sprite = new GameOverView();
public function Main() {
//MT.init(this,2);
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
addIntroViewF();
}
private function addIntroViewF():void{
addChild(introView);
introView.addEventListener(“startGameE”,startGameF,false,0,true);
}
private function startGameF(e:Event):void{
removeIntroViewF();
addGameViewF();
}
private function removeIntroViewF():void{
removeChild(introView);
introView.removeEventListener(“startGameE”,startGameF,false);
387
388
Chapter 10 n 3D Game Development
}
private function addGameViewF():void{
addChild(gameView);
gameView.addEventListener(“gameOverE”,gameOverF,false,0,true);
}
private function gameOverF(e:Event):void{
removeGameViewF();
addGameOverViewF();
}
private function removeGameViewF():void{
removeChild(gameView);
gameView.removeEventListener(“gameOverE”,gameOverF,false);
}
private function addGameOverViewF():void{
addChild(gameOverView);
gameOverView.addEventListener(“replayE”,replayF,false,0,true);
}
private function replayF(e:Event):void{
removeGameOverViewF();
addIntroViewF();
}
private function removeGameOverViewF():void{
removeChild(gameOverView);
gameOverView.removeEventListener(“replayE”,replayF,false);
}
}
}
IntroView
package com.kglad {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
public class IntroView extends Sprite{
private var ic:InstructionsControls = new InstructionsControls();
private var startGameE:Event = new Event(“startGameE“);
public function IntroView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
this.addChild(ic);
Version 01
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF,false,0,true);
}
private function keydownF(e:KeyboardEvent):void{
if(e.keyCode==32){
dispatchEvent(startGameE);
}
}
private function cleanupF(e:Event):void{
stage.removeEventListener(KeyboardEvent.KEY_DOWN,keydownF,false);
}
}
}
Version_01 GameView
package com.kglad {
import flare.basic.Scene3D;
import flare.basic.Viewer3D;
import flare.core.Pivot3D;
import flare.loaders.Flare3DLoader1;
import flash.display.*;
import flash.events.*;
public class GameView extends Sprite {
// a Scene3D instance is the first object needed to
// create a 3d scene.
private var scene:Scene3D;
// planet is a 3d model that will be imported
// (models/planet.f3d)
private var planet:Pivot3D;
public function GameView() {
// The usual
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
// The first time the GameView instance is
// added to the stage, this branch executes.
// Thereafter, the else branch executes.
if(!scene){
// For this step, instead of using a Scene3D
// instance, use a Viewer3D instance. Viewer3D
// is a Scene3D subclass with some extra
// functionality. Namely, you can use your
389
390
Chapter 10 n 3D Game Development
// mouse to drag-rotate and scrollwheel-zoom.
//scene = new Scene3D(Sprite(root));
scene = new Viewer3D(Sprite(root));
// Check Scene3D in the API. Without this
// statement a default logo appears in the
// four corners of the stage.
//scene.showLogo = false;
// This prevents scene from updating, which
// at this point means this prevents scene
// from starting to render. When
// scene.resume() executes, scene resumes
// updating.
scene.pause();
// Check the API if you are unsure what
// this does.
scene.antialias = 1024;
// The Scene3D.PROGRESS_EVENT will be used
// in the next version to display a preloader.
scene.addEventListener( Scene3D.PROGRESS_EVENT,
progressF );
// The Scene3D.COMPLETE_EVENT is used to indicate
// when the scene is ready to render (and then
// start the game action).
scene.addEventListener(Scene3D.COMPLETE_EVENT,
completeF );
// This is needed when using the Scene3D method
// addChildFromFile(), which adds an object from
// a file, or you can use just Flare3DLoader1 on
// a line by itself.
scene.registerClass( Flare3DLoader1);
//
//
//
//
//
//
//
//
//
//
//
//
//
This loads the planet model (models/planet.f3d)
from an external file and stores a reference in
a Pivot3D instance. The Pivot3D class is the
most basic 3D element in Flare.
The f3d file format is a Flare3D file format
(per the Flare website), but it started as
an open source file format for the storage
of volumetric data. Flare3D can
also import Collada and Obj files, so you
can use 3D Max, Maya, Blender, and Art of
Illusion to create 3D models. Blender and Art
of Illusion are free, with Art of Illusion
being easier to learn but without all the
Version 01
// features of Blender.
planet = scene.addChildFromFile( “models/planet.f3d” );
} else {
// Everything is already loaded, so no need
// for a progress or complete event.
startGame();
scene.resume();
}
}
private function progressF(e:Event):void {
// Nothing here yet, but scene.loadProgress returns
// a number between 0 and 100 representing the
// percentage of the scene that is loaded.
}
private function completeF(e:Event):void {
// startGame() will be used to set up some of
// the game elements.
startGame();
// Render the scene.
scene.resume();
}
private function startGame():void {
// To be done.
}
private function cleanupF(e:Event):void{
// Again, this prevents the scene from updating.
// There’s no need to use CPU/GPU cycles when
// the GameView instance is removed from the
// stage. (Though scene will still be visible.)
scene.pause();
}
}
}
GameOverView
package com.kglad {
import flash.display.Sprite
import flash.events.Event;
import flash.events.KeyboardEvent;
public class GameOverView extends Sprite{
private var scoreDisplay:Score = new Score();
private var replayE:Event = new Event(“replayE“);
public function GameOverView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
391
392
Chapter 10 n 3D Game Development
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
stage.addEventListener(KeyboardEvent.KEY_DOWN,keydownF,false,0,true);
this.addChild(scoreDisplay);
scoreDisplay.tf.text = Data.score.toString();
scoreDisplay.best.tf.text = Data.bestScore.toString();
}
private function keydownF(e:KeyboardEvent):void{
if(e.keyCode==32){
dispatchEvent(replayE);
}
}
private function cleanupF(e:Event):void{
stage.removeEventListener(KeyboardEvent.KEY_DOWN,keydownF,
false);
}
}
}
Data
package com.kglad {
public class Data {
private static var _score:int;
private static var _bestScore:int;
public static function get score():int{
return _score;
}
public static function get bestScore():int{
return _bestScore;
}
public static function set score(n:int):void{
_score = n;
if(_score>_bestScore){
_bestScore = n;
}
}
}
}
Version 02
Test that to see the initial game. Use your mouse to rotate and zoom the planet.
There’s nothing else encoded, so checking the planet is all you can do with that first
version, and that planet defines the extent of the 3D world for this game.
In general, with 3D games you will need to initialize the world and the objects in the
world. You will also need to initialize and control a camera.
Further, you will probably have an object (or objects) that move through your 3D
world. All the objects and the camera in your 3D world will need to have a 3D position (or a location in the world) and a 3D rotation (or orientation in the world). That
is, they will all, at a minimum, need a position and need to face some direction in
order to define their appearance to the viewer.
There are other things you could consider adding to a 3D game, such as a view (how
the 3D world is projected onto a 2D plane), lights, and shadows, but I won’t discuss
those.
In this next version of GameView, we’re going to add the camera, an astro character
that can be moved through the 3D world (using the keyboard arrow keys), and an
energy 3D model, and we’ll animate the fans and mines that are part of the planet
model. We will also add the preloader display.
Version 02
Version_02 GameView
package com.kglad {
import flare.basic.Scene3D;
import flare.basic.Viewer3D;
import flare.loaders.Flare3DLoader1
import flare.core.*;
import flare.utils.*;
import flare.system.Input3D;
import flare.collisions.*
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class GameView extends Sprite {
private var scene:Scene3D;
private var planet:Pivot3D;
private var astro:Pivot3D;
private var astroP:Pivot3D;
private var shadow:Pivot3D;
private var sky:Pivot3D;
private var energy:Pivot3D;
393
394
Chapter 10 n 3D Game Development
private var mineA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var fanA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var pointA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var mine:Pivot3D;
private var fan:Pivot3D;
private var i:int;
private var rayCollision:RayCollision;
private var sphereCollision:SphereCollision;
private var astroAbove:Vector3D = new Vector3D(0,100,0);
private var astroFrom:Vector3D = new Vector3D();
private var astroDown:Vector3D;
private var info:CollisionInfo;
private var startPoint:Pivot3D;
private var energyIndex:int;
private var astroPosition:Vector3D;
// Initialize the 2D preloader display. The Loading class
// object is in the yp.fla library.
private var loading:Loading = new Loading();
public function GameView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
if(!scene){
// Standard code to add the preloader to
// the display
addChild( loading );
scene = new Scene3D(Sprite(root));
scene.showLogo = false;
scene.pause();
scene.antialias = 1024;
// Create a 3D camera. Code to control
// the camera’s location and orientation
// are below.
scene.camera = new Camera3D();
scene.addEventListener(Scene3D.PROGRESS_EVENT,
progressF );
scene.addEventListener( Scene3D.COMPLETE_EVENT,
completeF );
scene.registerClass( Flare3DLoader1);
// Loads the external files and stores the
// references into planet and astro objects.
planet = scene.addChildFromFile( “models/planet.f3d” );
Version 02
astro = scene.addChildFromFile( “models/astronaut.f3d”
);
// Loads a (pseudo) shadow model for astro
// and an energy boost model.
shadow = scene.addChildFromFile( “models/shadow.f3d” );
energy = scene.addChildFromFile( “models/energy.f3d” );
} else {
startGame();
scene.resume();
// The Scene3D.UPDATE_EVENT is the Flare
// equivalent of Event.ENTER_FRAME and is
// an essential part of animating a Flare game.
// You should not use Event.ENTER_FRAME to
// animate a Flare game, because your
// Event.ENTER_FRAME loop will almost
// certainly be out of sync with your 3D
// scene. At a minimum, if you used your
// own loop to animate, you would need
// to disable scene.enableUpdateAndRender
// until scene.context returned true.
// Then you would need to direct Flare to
// update your scene animations, clear a
// back buffer, render the scene, and
// finally present the results.
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
}
private function progressF(e:Event):void {
// Here is the code to display loading progress
// of the external files
loading.bar.gotoAndStop( int(scene.loadProgress) );
}
private function completeF(e:Event):void {
// Remove the preloader. It will not be needed again,
// so it can be nulled.
removeChild(loading);
loading = null;
// This creates an empty Pivot3D object to be used
// as the astro parent. This is used later to control
// scene.camera
astroP = new Pivot3D();
// Add astro and his shadow to the parent.
astroP.addChild(astro);
astroP.addChild(shadow);
395
396
Chapter 10 n 3D Game Development
// Add astroP to the scene.
scene.addChild(astroP);
// planet has a number of Pivot3D children. The
// getChildByName() method can be used to obtain
// references to them.
sky = planet.getChildByName( “sky” );
// A RayCollision instance is used to create
// a virtual (i.e., nothing is displayed) line
// that can be used to determine
// whether and where an object moving in a
// straight line will impact another object.
// In this game, it is used to determine where
// astro should be positioned so he appears to
// be rooted to the planet surface (or
// floor). A ray extending from above astro’s
// head through his feet will impact the planet
// floor. The x, y, and z properties
// of that impact point are used to assign
// astro’s x, y, and z properties.
rayCollision = new RayCollision();
// The second parameter in addCollisionWith()
// indicates whether children are included in
// the collision detection.
rayCollision.addCollisionWith( planet.getChildByName( “floor” ),
false );
// The forEach() method calls a function for each
// of the children of the planet.
planet.forEach(planetF);
// Starts the level.
startGame();
// Continues rendering the scene.
scene.resume();
// This important line of code was discussed above.
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function planetF(p3d:Pivot3D):void{
// Here, fanA, mineA, and pointA vectors are populated
// with object references that will be used when
// updating the scene.
if(p3d.name.indexOf(“fan“)>-1){
// Pivot3D instances have a userData property
// that has type Object. So, it can be used to
// store an unlimited amount of data needed
// by the instance.
Version 02
// randomF() is below
p3d.userData = {speed:randomF(5,10)};
fanA.push(p3d);
} else if(p3d.name.indexOf(“mine“)>-1){
p3d.userData = {speed:randomF(-20,-10)};
mineA.push(p3d);
} else if (p3d.name.indexOf(“point“)>-1 ){
// points to position energy
pointA.push(p3d);
}
}
private function startGame():void {
// startPoint is just an empty Pivot3D instance
// on the planet floor used to initially
// position astro.
startPoint = planet.getChildByName( “start” );
// The Pivot3D copyTransformFrom() method is a
// quick way to copy the position and orientation
// of the argument (startPoint) to astroP.
astroP.copyTransformFrom( startPoint );
// astro is an animated 3D model with frames.
// Frame 6 (0-based) appears close to standing still.
astro.gotoAndStop(6);
// (Re)set some values.
astro.y = 0;
// randomize positions of energy boosts.
shuffle(pointA);
// position & orient energy instance
energy.copyTransformFrom( pointA[energyIndex] );
}
// updateF() is the game loop listener function.
private function updateF(e:Event):void {
// Update astro’s position and orientation.
astroF();
// Update the objects (mines and fans)
worldObjectsF();
// Update the camera position and orientation.
cameraF();
}
private function astroF():void{
// astroAbove (initialized above and
// = new Vector3D(0,100,0) )is
// a vector3D instance that is 100 px
// above astro (in local coordinates)
397
398
Chapter 10 n 3D Game Development
// astroFrom is a Vector3D instance in
// global coordinates that will be
// rayCollisions start point
astroFrom= astroP.localToGlobal(astroAbove);
// Pivot3D instances have getLeft(), getRight(),
// getUp(), and getDown() methods that return the
// Vector3D direction to the left, right, up, and
// down of the Pivot3D instance. (There is also a
// getDir() method that returns the direction to
// the front of the Pivot3D instance.)
// Here we want the down direction to use with
// rayCollision that is starting just above astro
// and will extend down through his
// feet (and onwards). Where that ray collides with
// the planet floor is where astro should be
// positioned.
astroDown = astroP.getDown();
// Test the ray using a “from” Vector3D and a
// “direction” Vector3D
if (rayCollision.test(astroFrom, astroDown)) {
// The data Vector (like an Array, not a
// Vector3D) contains CollisionInfo elements
// with information about each
// object with a positive collision.
// If the addCollisionWith() second parameter
// is true, there may be more than one object
// in data.
info = rayCollision.data[0];
// Root astroP at the collision point.
astroP.setPosition(info.point.x, info.point.y,
info.point.z );
// Align the astroP to the collision
// normal (= perpendicular) so astro appears to
// be standing upright (and not leaning or upside
// down). The second parameter in
// setNormalOrientation() is an easing parameter
// (zero to one with default = 1).
astroP.setNormalOrientation( info.normal, 0.05 );
}
//
//
//
//
//
Flare3D has an Input3D class with static
methods that facilitate quick coding for
detecting keyboard input. rotateY() is a
Pivot3D method that accepts three parameters.
The first is the amount (in degrees) to rotate.
Version 02
//
//
//
//
//
//
//
if
The second is a Boolean indicating whether to use
local coordinates (default = true), and the third
is a Vector3D pivot point (default = null).
Of course, there are also rotateX and rotateZ
methods. Notice these methods work differently
from the Flash rotationX, rotation, and rotationZ
properties.
( Input3D.keyDown( Input3D.RIGHT ) ) {
astroP.rotateY( 2 );
}
if ( Input3D.keyDown( Input3D.LEFT ) ) {
astroP.rotateY( -2 );
}
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( 1 );
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -1 );
}
}
// Update the world objects.
private function worldObjectsF():void{
// The getPosition() Pivot3D method returns a position
// Vector3D instance. It accepts two parameters. The
// first indicates whether the returned Vector3D is
// in local coordinates (default = true), and the second
// accepts a Vector3D if you want to assign the return
// value to that second parameter. The default is null.
// This is an example where I found the API lacking.
// It is not clear to me when or why a second parameter
// should be used.
astroPosition = astro.getPosition(false,null);
// astroPosition is used to determine when astro is
// close to a fan or mine, which will be used in
// another version.
for(i=fanA.length-1;i>=0;i--){
fan = fanA[i];
fan.rotateY( fan.userData.speed, true, null );
}
for(i=mineA.length-1;i>=0;i--){
mine = mineA[i];
mine.rotateY(mine.userData.speed);
}
// This adds a little ambiance.
399
400
Chapter 10 n 3D Game Development
sky.rotateX(0.1);
}
private function cameraF():void{
// The Pivot3DUtils class has two static methods used
// here to link the position and orientation of an
// object (scene.camera in the case) with another object
// (astroP in this case).
// The first method is used to position the camera (0px)
// to the right, (40px) above and (30px) behind astroP.
// The last parameter in
// Pivot3DUtils.setPositionWithReference() is an easing
// parameter between 0 and 1 with default = 1.
Pivot3DUtils.setPositionWithReference(scene.camera, 0, 40, -30,
astroP, 0.1);
// The second method is used to orient (or rotate)
// the camera toward the front of astroP.
// Again, second, third, and fourth parameters are
// the x, y, and z offsets relative to astroP. The
// last two parameters are the up direction of
// scene.camera and an easing parameter.
Pivot3DUtils.lookAtWithReference(scene.camera, 0, 0, 0,
astroP, astroP.getUp(), 0.2 );
}
private function cleanupF(e:Event):void{
scene.pause();
scene.removeEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function shuffle(a:Vector.<Pivot3D>) {
var i:int;
var j:int;
var e:*;
var len:int = a.length;
for (i = len-1; i>=0; i--) {
j=Math.floor((i+1)*Math.random());
e = a[i];
a[i] = a[j];
a[j] = e;
}
}
private function randomF(n1:Number,n2:Number):Number{
if(n1>n2){
return n2+(n1-n2)*Math.random();
} else {
return n1+(n2-n1)*Math.random();
Version 03
}
}
}
}
Test that and explore the world. Explore the area around a larger fan’s perimeter.
Notice how astro stays perpendicular to the planet surface as you move in and out
of that perimeter. But also notice how astro can leave the planet surface when you
orient him such that rayCollision fails to intersect the planet. That won’t be a problem in the next version because we’re going to add collision detection with objects so
that astro won’t be able to walk through objects.
Version 03
Version_03 GameView
package com.kglad {
import flare.basic.Scene3D;
import flare.basic.Viewer3D;
import flare.loaders.Flare3DLoader1
import flare.core.*;
import flare.utils.*;
import flare.system.Input3D;
import flare.collisions.*
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class GameView extends Sprite {
private var scene:Scene3D;
private var planet:Pivot3D;
private var astro:Pivot3D;
private var astroP:Pivot3D;
private var shadow:Pivot3D;
private var sky:Pivot3D;
private var energy:Pivot3D;
private var mineA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var fanA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var pointA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var mine:Pivot3D;
private var fan:Pivot3D;
private var i:int;
private var rayCollision:RayCollision;
private var sphereCollision:SphereCollision;
private var astroAbove:Vector3D = new Vector3D(0,100,0);
private var astroFrom:Vector3D = new Vector3D();
401
402
Chapter 10 n 3D Game Development
private
private
private
private
var
var
var
var
astroDown:Vector3D;
mineDown:Vector3D;
info:CollisionInfo
startPoint:Pivot3D;
private var state:String = “run“;
private var speed:Number;
private var level:Number;
private var jumpValue:Number;
private var running:Boolean;
private var energyCount:int;
private var energyIndex:int;
private var shakeFactor:Number;
private var resetCounter:int;
private var radius:Number;
private var astroPosition:Vector3D;
private var loading:Loading = new Loading();
private var gameOverE:Event = new Event(“gameOverE“);
public function GameView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
speed = 0.75;
jumpValue = 0;
energyCount = 0;
if(!scene){
addChild( loading );
scene = new Scene3D(Sprite(root));
scene.showLogo = false;
scene.pause();
scene.antialias = 1024;
scene.camera = new Camera3D();
scene.addEventListener( Scene3D.PROGRESS_EVENT, progressF );
scene.addEventListener( Scene3D.COMPLETE_EVENT, completeF);
scene.registerClass( Flare3DLoader1);
planet = scene.addChildFromFile( “models/planet.f3d” );
astro = scene.addChildFromFile( “models/astronaut.f3d” );
shadow = scene.addChildFromFile( “models/shadow.f3d” );
energy = scene.addChildFromFile( “models/energy.f3d” );
} else {
startGame();
Version 03
scene.resume();
scene.addEventListener(Scene3D.UPDATE_EVENT, updateF );
}
}
private function progressF(e:Event):void {
loading.bar.gotoAndStop( int(scene.loadProgress) );
}
private function completeF(e:Event):void {
removeChild(loading);
astroP = new Pivot3D();
astroP.addChild(astro);
astroP.addChild(shadow);
scene.addChild(astroP);
sky = planet.getChildByName( “sky” );
rayCollision = new RayCollision();
rayCollision.addCollisionWith( planet.getChildByName( “floor” ),
false );
// The SphereCollision() constructor accepts
// three parameters. The first (source) is a
// Pivot3D instance, which will have a virtual
// sphere around it to detect collisions with
// other world objects. The second is a Number
// (default = 1), the radius of the virtual
// sphere, and the third is a local (to the first
// parameter) Vector3D offset from the source’s
// 0,0,0 point. In this example, astroP is the
// source and 0,0,0 of astro (the only object
// in astroP) is at his feet, so an offset of
// (0,3,0) will move the virtual sphere’s center
// to astro’s center.
sphereCollision = new SphereCollision( astroP, 3, new Vector3D( 0, 3, 0 ) );
planet.forEach(planetF);
startGame();
scene.resume();
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function planetF(p3d:Pivot3D):void{
if(p3d.name.indexOf(“fan“)>-1){
p3d.userData = {speed:randomF(5,10)};
fanA.push(p3d);
} else if(p3d.name.indexOf(“mine“)>-1){
p3d.userData = {speed:randomF(-20,-10)};
403
404
Chapter 10 n 3D Game Development
mineA.push(p3d);
} else if (p3d.name.indexOf(“obstacle“)>-1 ){
// The SphereCollision class has an
// addCollisionWith() method that allows you
// to add Pivot3D instances to be used for
// collision testing. In this case, if the
// object is an obstacle, it is added to the
// collection of objects to be used for
// collision testing. The second parameter
// (default = true) indicates whether
// Pivot3D children are used for testing.
sphereCollision.addCollisionWith(p3d, false );
} else if (p3d.name.indexOf(“point“)>-1 ){
pointA.push(p3d);
}
}
private function startGame():void {
startPoint = planet.getChildByName( “start” );
astroP.copyTransformFrom( startPoint );
astro.gotoAndStop(6);
// astroP will be made not visible when he dies.
astroP.visible = true;
// A state variable will be used in astroF() to
// determine what astro is doing.
state = “run“;
// A running variable used in astroF() to determine
// when astro starts running.
running = false;
// shakeFactor is used to “shake” the camera when
// astro jumps and collides with fans and mines
shakeFactor = 0;
shuffle(pointA);
energy.copyTransformFrom( pointA[energyIndex] );
// The SphereCollision class uses the previous position
// and trajectory of the source to determine collisions.
// If either were reassigned, the reset() method should
// be applied to sphereCollision. I did not reassign
// those, so the following is unneeded in this game.
// sphereCollision.reset();
}
private function updateF(e:Event):void {
astroF();
worldObjectsF();
cameraF();
Version 03
}
private function astroF():void{
astroFrom= astroP.localToGlobal(astroAbove);
astroDown = astroP.getDown();
if (rayCollision.test(astroFrom, astroDown)) {
info = rayCollision.data[0];
astroP.setPosition( info.point.x, info.point.y,
info.point.z );
astroP.setNormalOrientation( info.normal, 0.05 );
}
// When astroP moves, test for collisions. If astroP
// is colliding with something, the slider() method
// will yield a displacement dependent on the collision
// angle and will position astroP accordingly.
// Contrast slider() to the fixed() method that
// positions astroP at the collision point. There is
// also an intersect() method that does no
// repositioning of astroP.
sphereCollision.slider();
// Here is where the start variable is used to
// determine what astro and astroP should do.
switch( state ){
case “run“:
if ( Input3D.keyDown( Input3D.RIGHT ) ) {
astroP.rotateY( 2 );
if(!running){
// Here is where the variable
// running is used to prevent
// repeatedly executing
// astro.gotoAndPlay()
running = true;
// The Pivot3D gotoAndPlay()
// accepts three parameters.
// The first parameter is
// frame label or a frame
// number similar to the
// Flash first gotoAndPlay()
// parameter. The second is
// blendFrames, which is the
// number (default = 0) of
// frames blended when
// going from one animation
// to another. The third
// parameter is a loop int
405
406
Chapter 10 n 3D Game Development
// with default = 0
// (regular looping), 1 for
// ping-pong looping, and 2
// to stop at the end of the
// animation sequence.
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.RIGHT ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyDown( Input3D.LEFT ) ) {
astroP.rotateY( -2 );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.LEFT ) ) {
running = false;
astro.stop();
}
//astro.gotoAndStop( “run”,3);
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( speed );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0);
}
} else if ( Input3D.keyUp( Input3D.UP ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -speed );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.DOWN ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyHit( Input3D.SPACE ) ) {
// jumpValue is used below to update
Version 03
// astro’s y property.
jumpValue = 4;
state = “jump“;
astro.gotoAndPlay( “jump”, 3);
}
break;
case “jump“:
if ( astro.y == 0 ){
// astro’s returned to the planet
// surface.
state = “run“;
if(!running){
astro.gotoAndStop(6);
}
}
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( speed );
running = true;
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -speed );
running = true
}
break;
case “fan“:
// astro collided with a fan
jumpValue = 4;
astroP.rotateY(1);
shakeFactor = 1;
// Because state not changed here, jumpValue
// keeps resetting to 4 and astroP continues
// to rotate and rise above the planet surface.
// When astro rises above 500, reposition
// astro at the start (and change state
// to “run“).
if ( astro.y > 500 ) {
startGame();
}
break;
case “energy“:
// astro collided with the energy
// boost resetCounter acts like a
// timer. Used here it stops astro
// from moving while a level-up
407
408
Chapter 10 n 3D Game Development
// message is displayed.
resetCounter--;
if ( resetCounter < 0 ) state = “run“;
break;
case “die“:
// astro died. Delay dispatching the
// gameOverE event.
resetCounter--;
if ( resetCounter < 0 ) {
dispatchEvent(gameOverE);
}
break;
}
if(jumpValue!=0){
// Apply gravity to slow astro’s rise and then
// accelerate his fall.
jumpValue -= 0.3;
// Update astro’s y property.
astro.y += jumpValue;
// Stop astro’s jump
if ( astro.y < 0 ) {
jumpValue = 0;
astro.y = 0;
}
}
}
private function worldObjectsF():void{
astroPosition = astro.getPosition(false);
for(i=fanA.length-1;i>=0;i--){
fan = fanA[i];
fan.rotateY( fan.userData.speed );
radius = fan.scaleX * 15;
// If the distance between the astronaut and
// fan is less than the fan radius, change
// state to indicate a fan collision. Note if
// you jump and are high enough over the
// fan, this conditional will be false.
if (Vector3D.distance( fan.getPosition(), astroPosition )
< radius ){
state = “fan“;
}
}
for(i=mineA.length-1;i>=0;i--){
Version 03
mine = mineA[i];
mine.rotateY(mine.userData.speed);
// After destroying a mine, it is made not
// visible. So, only want to destroy it again
// (or allow it to kill astro) if it is visible
if ( mine.visible && Vector3D.distance( mine.getPosition(),
astroPosition ) < 10 ){
if ( state == “jump” ){
// If the astro is jumping, destroy
// the mine.
mine.visible = false;
shakeFactor = 2;
}
else if ( state == “run” ){
// if astro is running, kill astro
astroP.visible = false;
shakeFactor = 15;
state = “die“;
resetCounter = 120;
}
}
//
//
//
if
Check if mine was destroyed. If it was and it
is on the hemisphere opposite of astro,
reactivate it (i.e., make it visible).
(!mine.visible) {
// The dot product of two vectors is
// negative if the angle between them
// is between 90 and 270 degrees.
// That is, if the two vectors point
// “away” from each other, their dot
// product is negative.
mineDown = mine.getDown();
astroDown = astroP.getDown();
if ( mineDown.dotProduct( astroDown ) < 0 ){
mine.visible = true;
}
}
}
if ( Vector3D.distance( energy.getPosition(), astroPosition ) < 10){
// Increment energyIndex and move the energy
// Pivot3D to another point.
energyIndex = (energyIndex+1)%pointA.length;
energy.copyTransformFrom( pointA[energyIndex] );
409
410
Chapter 10 n 3D Game Development
energyCount++;
}
sky.rotateX(0.1);
}
private function cameraF():void{
// 3rd person
Pivot3DUtils.setPositionWithReference(scene.camera, 0, 40, -30,
astroP, 0.1);
Pivot3DUtils.lookAtWithReference(scene.camera, 0, 0, 0, astroP,
astroP.getUp(), 0.2 );
if ( shakeFactor > 0 ){
scene.camera.x += Math.random() * shakeFactor;
scene.camera.y += Math.random() * shakeFactor;
scene.camera.z += Math.random() * shakeFactor;
shakeFactor *= 0.9;
}
}
private function cleanupF(e:Event):void{
scene.pause();
scene.removeEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function shuffle(a:Vector.<Pivot3D>) {
var i:int;
var j:int;
var e:*;
var len:int = a.length;
for (i = len-1; i>=0; i--) {
j=Math.floor((i+1)*Math.random());
e = a[i];
a[i] = a[j];
a[j] = e;
}
}
private function randomF(n1:Number,n2:Number):Number{
if(n1>n2){
return n2+(n1-n2)*Math.random();
} else {
return n1+(n2-n1)*Math.random();
}
}
}
}
Version 04
Version 03 is nearly a complete game. We just need to add some scoring and sounds
to make the game come alive. We also added scaling of astro’s shadow when he
jumps and after he collides with a fan.
Version 04
Version_04 GameView
package com.kglad {
import flare.basic.Scene3D;
import flare.basic.Viewer3D;
import flare.loaders.Flare3DLoader1
import flare.core.*;
import flare.utils.*;
import flare.system.Input3D;
import flare.collisions.*
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class GameView extends Sprite {
private var scene:Scene3D;
private var planet:Pivot3D;
private var astro:Pivot3D;
private var astroP:Pivot3D;
private var shadow:Pivot3D;
private var sky:Pivot3D;
private var energy:Pivot3D;
private var mineA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var fanA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var pointA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var mine:Pivot3D;
private var fan:Pivot3D;
private var i:int;
private var rayCollision:RayCollision;
private var sphereCollision:SphereCollision;
private var astroAbove:Vector3D = new Vector3D(0,100,0);
private var astroFrom:Vector3D = new Vector3D();
private var astroDown:Vector3D;
private var mineDown:Vector3D;
private var info:CollisionInfo
private var startPoint:Pivot3D;
private var energyGUI:EnergyGUI;
private var pointsGUI:PointsGUI;
private var levelGUI:LevelGUI;
411
412
Chapter 10 n 3D Game Development
private var state:String = “run“;
private var speed:Number;
private var level:Number;
private var jumpValue:Number;
private var running:Boolean;
private var score:int;
private var bestScore:int;
private var energyCount:int;
private var energyIndex:int;
private var shakeFactor:Number;
private var resetCounter:int;
private var radius:Number;
private var astroPosition:Vector3D;
private var loading:Loading = new Loading();
// Create Sound instances
private var sndDead:DeadSound = new DeadSound();
private var sndFan:FanSound = new FanSound();
private var sndJump:JumpSound = new JumpSound();
private var sndMine:MineSound = new MineSound();
private var sndReset:ResetSound = new ResetSound();
private var landSound:LandSound = new LandSound();
private var gameOverE:Event = new Event(“gameOverE“);
public function GameView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
speed = 0.75;
jumpValue = 0;
energyCount = 0;
level = 0;
Data.score = 0;
score = 0;
if(!scene){
// Initialize the user interface (power boost
// display, level display and points display);
initGUI();
addChild( loading );
scene = new Scene3D(Sprite(root));
scene.showLogo = false;
scene.pause();
scene.antialias = 1024;
scene.camera = new Camera3D();
Version 04
scene.addEventListener(Scene3D.PROGRESS_EVENT, progressF );
scene.addEventListener( Scene3D.COMPLETE_EVENT, completeF);
scene.registerClass(Flare3DLoader1);
planet = scene.addChildFromFile( “models/planet.f3d” );
astro = scene.addChildFromFile( “models/astronaut.f3d” );
shadow = scene.addChildFromFile( “models/shadow.f3d” );
energy = scene.addChildFromFile( “models/energy.f3d” );
} else {
startGame();
scene.resume();
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
}
private function progressF(e:Event):void {
loading.bar.gotoAndStop( int(scene.loadProgress) );
}
private function completeF(e:Event):void {
removeChild(loading);
astroP = new Pivot3D();
astroP.addChild(astro);
astroP.addChild(shadow);
scene.addChild(astroP);
sky = planet.getChildByName( “sky” );
rayCollision = new RayCollision();
rayCollision.addCollisionWith( planet.getChildByName( “floor” ),
false );
sphereCollision = new SphereCollision( astroP, 3, new Vector3D( 0,
3, 0 ) );
planet.forEach(planetF);
startGame();
scene.resume();
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function planetF(p3d:Pivot3D):void{
if(p3d.name.indexOf(“fan“)>-1){
p3d.userData = {speed:randomF(5,10)};
fanA.push(p3d);
} else if(p3d.name.indexOf(“mine“)>-1){
p3d.userData = {speed:randomF(-20,-10)};
mineA.push(p3d);
} else if (p3d.name.indexOf(“obstacle“)>-1 ){
sphereCollision.addCollisionWith(p3d, false );
} else if (p3d.name.indexOf(“point“)>-1 ){
413
414
Chapter 10 n 3D Game Development
pointA.push(p3d);
}
}
private function startGame():void {
startPoint = planet.getChildByName( “start” );
astroP.copyTransformFrom( startPoint );
astro.gotoAndStop(6);
astroP.visible = true;
state = “run“;
running = false;
shakeFactor = 0;
shuffle(pointA);
energy.copyTransformFrom( pointA[energyIndex] );
}
private function updateF(e:Event):void {
astroF();
worldObjectsF();
cameraF();
}
private function astroF():void{
astroFrom= astroP.localToGlobal(astroAbove);
astroDown = astroP.getDown();
if (rayCollision.test(astroFrom, astroDown)) {
info = rayCollision.data[0];
astroP.setPosition( info.point.x, info.point.y,
info.point.z );
astroP.setNormalOrientation( info.normal, 0.05 );
}
sphereCollision.slider();
switch( state ){
case “run“:
if ( Input3D.keyDown( Input3D.RIGHT ) ) {
astroP.rotateY( 2 );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.RIGHT ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyDown( Input3D.LEFT ) ) {
astroP.rotateY( -2 );
if(!running){
Version 04
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.LEFT ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( speed );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.UP ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -speed );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.DOWN ) ) {
running = false;
astro.stop();
}
if ( Input3D.keyHit( Input3D.SPACE ) ) {
jumpValue = 4;
state = “jump“;
astro.gotoAndPlay( “jump”, 3 );
sndJump.play();
}
break;
case “jump“:
if ( astro.y == 0 ){
state = “run“;
if(!running){
astro.gotoAndStop(6);
}
}
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( speed );
415
416
Chapter 10 n 3D Game Development
running = true;
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -speed );
running = true
}
break;
case “fan“:
jumpValue = 4;
astroP.rotateY(1);
shakeFactor = 1;
if ( astro.y > 500 ) {
startGame();
}
break;
case “energy“:
resetCounter--;
if ( resetCounter < 0 ) state = “run“;
break;
case “die“:
resetCounter--;
if ( resetCounter < 0 ) {
dispatchEvent(gameOverE);
}
// Update Data.score
Data.score = score;
break;
}
if(jumpValue!=0){
jumpValue -= 0.3;
astro.y += jumpValue;
// Scale the shadow. If returning to planet after
// fan collision, probably start==“run” and
// running=true. Else this is a regular jump.
if(state==“run” && !running){
shadow.scaleX = shadow.scaleZ = Math.max(.01,(500astro.y)/500);
} else {
shadow.scaleX = shadow.scaleZ = Math.max(.01,(25astro.y)/25);
}
if ( astro.y < 0 ) {
if(state==“run” && !running){
// If landing after a fan collision
Version 04
// play landSound
landSound.play();
}
jumpValue = 0;
astro.y = 0;
}
}
}
private function worldObjectsF():void{
astroPosition = astro.getPosition(false);
for(i=fanA.length-1;i>=0;i--){
fan = fanA[i];
fan.rotateY( fan.userData.speed );
radius = fan.scaleX * 15;
if ( Vector3D.distance( fan.getPosition(), astroPosition )
< radius ){
state = “fan“;
score -= 100;
// Update the score display
updateGUI();
// Display PopNeg100 MovieClip
newPop(-1);
// play sndFan
sndFan.play();
}
}
for(i=mineA.length-1;i>=0;i--){
mine = mineA[i];
mine.rotateY(mine.userData.speed);
if ( mine.visible && Vector3D.distance( mine.getPosition(),
astroPosition ) < 10 ){
if ( state == “jump” ){
mine.visible = false;
shakeFactor = 2;
score += 100;
updateGUI();
// Play the mine sound
sndMine.play();
// Display Pop100 MovieClip
newPop(1);
}
else if ( state == “run” ){
// It the astronaut was running,
// astro die! :(
417
418
Chapter 10 n 3D Game Development
astroP.visible = false;
shakeFactor = 15;
state = “die“;
resetCounter = 120;
// play sndDead
sndDead.play();
// Display Crash MovieClip
newCrash();
}
}
if (!mine.visible) {
mineDown = mine.getDown();
astroDown = astroP.getDown();
if ( mineDown.dotProduct( astroDown ) < 0 ){
mine.visible = true;
}
}
}
if ( Vector3D.distance( energy.getPosition(), astroPosition ) < 10 ){
energyIndex = (energyIndex+1)%pointA.length;
energy.copyTransformFrom( pointA[energyIndex] );
score += 1000;
updateGUI();
energyCount++;
// For every three energy boosts
if ( energyCount == 3 ){
// Increment level
level++;
// Increase speed (used in astroF() )
speed += 0.2;
// Increase the frame rate of astro.
astro.frameSpeed += .1;
// Add the levelGUI display to show user
// they advanced a level.
addChild(levelGUI);
levelGUI.content.tf.text = level.toString();
levelGUI.play();
// reset energyCount
energyCount = 0;
// “Pause” user interaction with astro
// while levelGUI is displayed
resetCounter = 120;
state = “energy“;
Version 04
}
energyGUI.tf.text = energyCount.toString();
newPower();
sndReset.play();
}
sky.rotateX(0.1);
}
private function cameraF():void{
Pivot3DUtils.setPositionWithReference(scene.camera, 0, 40, -30,
astroP, 0.1);
Pivot3DUtils.lookAtWithReference(scene.camera, 0, 0, 0, astroP,
astroP.getUp(), 0.2 );
if ( shakeFactor > 0 ){
scene.camera.x += Math.random() * shakeFactor;
scene.camera.y += Math.random() * shakeFactor;
scene.camera.z += Math.random() * shakeFactor;
shakeFactor *= 0.9;
}
}
private function initGUI():void{
// Create and position user interface display.
energyGUI = new EnergyGUI();
addChild(energyGUI);
pointsGUI = new PointsGUI();
addChild(pointsGUI);
pointsGUI.x = stage.stageWidth-pointsGUI.width;
levelGUI = new LevelGUI();
levelGUI.x = stage.stageWidth/2;
levelGUI.y = stage.stageHeight/2
}
private function updateGUI():void {
// update the points display
pointsGUI.tf.text = score.toString();
}
// Create and display MovieClip when points added (Pop100)
// and subtracted (PopNeg100).
private function newPop(n:int):void{
var pos:Vector3D = astroP.getScreenCoords();
if(n>0){
var pop:MovieClip = new Pop100();
} else {
pop = new PopNeg100();
}
pop.x = pos.x;
419
420
Chapter 10 n 3D Game Development
pop.y = pos.y;
addChild( pop );
}
// When astro runs into a mine, display Crash MovieClip
private function newCrash():void{
var pos:Vector3D = astroP.getScreenCoords();
var crash:Crash = new Crash();
crash.x = pos.x;
crash.y = pos.y;
addChild( crash );
}
// Display Power MovieClip when energy boost gained.
private function newPower():void{
var pos:Vector3D = astroP.getScreenCoords();
var power:Power = new Power();
power.x = pos.x;
power.y = pos.y;
addChild( power );
}
private function cleanupF(e:Event):void{
scene.pause();
scene.removeEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function shuffle(a:Vector.<Pivot3D>) {
var i:int;
var j:int;
var e:*;
var len:int = a.length;
for (i = len-1; i>=0; i--) {
j=Math.floor((i+1)*Math.random());
e = a[i];
a[i] = a[j];
a[j] = e;
}
}
private function randomF(n1:Number,n2:Number):Number{
if(n1>n2){
return n2+(n1-n2)*Math.random();
} else {
return n1+(n2-n1)*Math.random();
}
}
}
}
Version 05
You can do much more with Flare3D, including adding physics (www.flare3d.com/
blog/2011/12/20/flare3d-physics-engine-beta) and 3D particle effects. Also, when you
extract the flare3D.zip package, you will create an examples directory that contains a
number of .as files with useful code snippets.
In the final version of Yellow Planet, we’ll add particle effects using the ParticleEmiter3D
class. We’ll also clean up some code; the commented code will show how you can
change from a third-person view to a first-person view.
Version 05
Version_05 GameView
package com.kglad {
import flare.basic.Scene3D;
import flare.basic.Viewer3D;
import flare.loaders.Flare3DLoader1
import flare.core.*;
import flare.utils.*;
import flare.system.Input3D;
import flare.collisions.*
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class GameView extends Sprite {
private var i:int;
private var j:int;
private var state:String = “run“;
private var speed:Number;
private var level:Number;
private var jumpValue:Number;
private var running:Boolean;
private var score:int;
private var bestScore:int;
private var energyCount:int;
private var energyIndex:int;
private var shakeFactor:Number;
private var resetCounter:int;
private var radius:Number;
private var angle:Number;
private const halfPI:Number = Math.PI/2;
private const threeHalvesPI:Number = 3*halfPI;
// 3d objects
private var scene:Scene3D;
421
422
Chapter 10 n 3D Game Development
private var planet:Pivot3D;
private var astro:Pivot3D;
private var astroP:Pivot3D;
private var shadow:Pivot3D;
private var sky:Pivot3D;
private var energy:Pivot3D;
private var mine:Pivot3D;
private var fan:Pivot3D;
private var startPoint:Pivot3D
// particles.
private var smokeTexture:Texture3D;
private var fireTexture:Texture3D;
private var fireEmitter:FireEmitter;
private var fanSmokeEmitter:SmokeEmitter ;
private var mineSmokeEmitter:SmokeEmitter;
// vectors
private var mineA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var fanA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var pointA:Vector.<Pivot3D> = new Vector.<Pivot3D>();
private var smokeA:Vector.<SmokeEmitter> = new Vector.<SmokeEmitter>();
// collision variables
private var astroPosition:Vector3D;
private var astroCollision:RayCollision;
private var sphereCollision:SphereCollision;
private var astroAbove:Vector3D = new Vector3D(0,100,0);
private var astroFrom:Vector3D = new Vector3D();
private var astroDown:Vector3D;
private var mineDown:Vector3D;
private var smokeDown:Vector3D;
private var info:CollisionInfo;
// GUI variables
private var energyGUI:EnergyGUI;
private var pointsGUI:PointsGUI;
private var levelGUI:LevelGUI;
// preloader
private var loading:Loading = new Loading();
// sounds.
private var sndDead:DeadSound = new DeadSound();
private var sndFan:FanSound = new FanSound();
private var sndJump:JumpSound = new JumpSound();
private var sndMine:MineSound = new MineSound();
private var sndReset:ResetSound = new ResetSound();
private var landSound:LandSound = new LandSound();
private var gameOverE:Event = new Event(“gameOverE“);
Version 05
public function GameView() {
this.addEventListener(Event.ADDED_TO_STAGE,init,false,0,true);
this.addEventListener(Event.REMOVED_FROM_STAGE,cleanupF,false,0,true);
}
private function init(e:Event):void{
if(!scene){
initGUI();
addChild( loading );
scene = new Scene3D(Sprite(root));
scene.showLogo = false;
scene.pause();
scene.antialias = 1024;
scene.camera = new Camera3D();
scene.addEventListener( Scene3D.PROGRESS_EVENT,
progressF );
scene.addEventListener( Scene3D.COMPLETE_EVENT,
completeF );
scene.registerClass( Flare3DLoader1);
planet = scene.addChildFromFile( “models/planet.f3d” );
astro = scene.addChildFromFile( “models/astronaut.f3d” );
shadow = scene.addChildFromFile( “models/shadow.f3d” );
energy = scene.addChildFromFile( “models/energy.f3d” );
// Textures used for particle emitters
smokeTexture = scene.addTextureFromFile( “assets/smoke.png” );
fireTexture = scene.addTextureFromFile( “assets/light.jpg”);
} else {
// I added a newGame() function to reset the
// score, level, etc.
newGame();
scene.resume();
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
}
private function progressF(e:Event):void {
loading.bar.gotoAndStop( int(scene.loadProgress) );
}
private function completeF(e:Event):void {
removeChild(loading);
scene.removeEventListener( Scene3D.PROGRESS_EVENT, progressF );
scene.removeEventListener( Scene3D.COMPLETE_EVENT, completeF );
astroP = new Pivot3D();
astroP.addChild(astro);
astroP.addChild(shadow);
scene.addChild(astroP);
423
424
Chapter 10 n 3D Game Development
// The FireEmitter and SmokeEmitter classes I copied and
// tweaked using trial and error. This is another place
// where the API is lacking.
fireEmitter = new FireEmitter(fireTexture);
// The fireEmitter display is below astro’s feet to
// be displayed when he jumps.
fireEmitter.parent = astro;
fireEmitter.y = -4;
sky = planet.getChildByName( “sky” );
astroCollision = new RayCollision();
astroCollision.addCollisionWith( planet.getChildByName( “floor” ),
false );
sphereCollision = new SphereCollision( astroP, 3, new Vector3D( 0,
3, 0 ) );
planet.forEach(planetF);
newGame();
scene.resume();
scene.addEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function planetF(p3d:Pivot3D):void{
if(p3d.name.indexOf(“fan“)>-1){
p3d.userData = {speed:randomF(5,10)};
fanA.push(p3d);
} else if(p3d.name.indexOf(“mine“)>-1){
p3d.userData = {speed:randomF(-20,-10),frequency:randomF(500,1000)};
mineA.push(p3d);
} else if (p3d.name.indexOf(“obstacle“)>-1 ){
sphereCollision.addCollisionWith(p3d, false );
} else if (p3d.name.indexOf(“point“)>-1 ){
pointA.push(p3d);
}
}
private function newGame():void{
speed = 0.75;
jumpValue = 0;
energyCount = 0;
level = 0;
Data.score = 0;
score = 0;
energyGUI.tf.text = energyCount.toString();
startGame();
}
Version 05
// newGame() is bypassed when astro collides with a fan.
private function startGame():void {
startPoint = planet.getChildByName( “start” );
astroP.copyTransformFrom( startPoint );
astro.gotoAndStop(6);
astroP.visible = true;
state = “run“;
running = false;
shakeFactor = 0;
shuffle(pointA);
energy.copyTransformFrom( pointA[energyIndex] );
}
private function updateF(e:Event):void {
astroF();
worldObjectsF();
cameraF();
updateGUI();
}
private function astroF():void{
astroFrom= astroP.localToGlobal(astroAbove);
astroDown = astroP.getDown();
if (astroCollision.test(astroFrom, astroDown)) {
info = astroCollision.data[0];
astroP.setPosition( info.point.x, info.point.y,
info.point.z );
astroP.setNormalOrientation( info.normal, 0.05 );
}
sphereCollision.slider();
switch( state ){
case “run“:
if ( Input3D.keyDown( Input3D.RIGHT ) ) {
astroP.rotateY( 2 );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.RIGHT ) ) {
running = false;
astro.gotoAndStop(6);
}
if ( Input3D.keyDown( Input3D.LEFT ) ) {
astroP.rotateY( -2 );
if(!running){
running = true;
425
426
Chapter 10 n 3D Game Development
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.LEFT ) ) {
running = false;
astro.gotoAndStop(6);
}
//astro.gotoAndStop( “run”,3);
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( speed );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.UP ) ) {
running = false;
astro.gotoAndStop(6);
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -speed );
if(!running){
running = true;
astro.gotoAndPlay( “run”,3,0 );
}
} else if ( Input3D.keyUp( Input3D.DOWN ) ) {
running = false;
astro.gotoAndStop(6);
}
if ( Input3D.keyHit( Input3D.SPACE ) ) {
jumpValue = 4;
// When astro jumps, fireEmitter
// emits particles.
fireEmitter.emitParticlesPerFrame = 25;
state = “jump“;
astro.gotoAndPlay( “jump”, 3 );
sndJump.play();
}
break;
case “jump“:
if ( astro.y == 0 ){
state = “run“;
if(!running){
astro.gotoAndStop(6);
}
}
Version 05
if ( Input3D.keyDown( Input3D.UP ) ) {
astroP.translateZ( speed );
running = true;
}
if ( Input3D.keyDown( Input3D.DOWN ) ) {
astroP.translateZ( -speed );
running = true
}
break;
case “fan“:
jumpValue = 4;
astroP.rotateY(1);
shakeFactor = 1;
if ( astro.y > 500 ) {
// fanSmokeEmitter created and
// parented in worldObjectsF()
// when astro is blown upwards
// by a fan.
fanSmokeEmitter.dispose();
startGame();
}
break;
case “energy“:
resetCounter--;
if ( resetCounter < 0 ) state = “run“;
break;
case “die“:
resetCounter--;
if ( resetCounter < 0 ) {
dispatchEvent(gameOverE);
}
Data.score = score;
break;
}
if(jumpValue!=0){
jumpValue -= 0.3;
astro.y += jumpValue;
if(state==“run” && !running){
shadow.scaleX = shadow.scaleZ = Math.max(.01,
(500-astro.y)/500);
} else {
shadow.scaleX = shadow.scaleZ = Math.max(.01,
(25-astro.y)/25);
}
427
428
Chapter 10 n 3D Game Development
if ( astro.y < 0 ) {
if(state==“run” && !running){
landSound.play();
}
jumpValue = 0;
astro.y = 0;
}
}
}
private function worldObjectsF():void{
astroPosition = astro.getPosition(false);
for(i=fanA.length-1;i>=0;i--){
fan = fanA[i];
fan.rotateY( fan.userData.speed );
radius = fan.scaleX * 15;
if ( Vector3D.distance( fan.getPosition(), astroPosition ) <
radius ){
state = “fan“;
score -= 100;
newPop(-1);
sndFan.play();
// Here is where fanSmokeEmitter is created.
// Only one can be created at any one time.
fanSmokeEmitter = new SmokeEmitter( smokeTexture
);
fanSmokeEmitter.copyTransformFrom( fan);
fanSmokeEmitter.parent = scene;
}
}
for(i=mineA.length-1;i>=0;i--){
mine = mineA[i];
mine.rotateY(mine.userData.speed);
if ( mine.visible && Vector3D.distance( mine.getPosition(),
astroPosition ) < 10 ){
if ( state == “jump” ){
mine.visible = false;
shakeFactor = 2;
score += 100;
sndMine.play();
newPop(1);
// When a mine is destroyed,
// a new SmokeEmitter is
// created and added.
Version 05
mineSmokeEmitter = new SmokeEmitter(
smokeTexture );
mineSmokeEmitter.copyTransformFrom(
mine);
mineSmokeEmitter.scaleX =
mineSmokeEmitter.scaleY = mineSmokeEmitter.scaleZ = .3;
mineSmokeEmitter.emitParticlesPerFrame = 4;
mineSmokeEmitter.parent = scene;
// More than one can exist at any
// one time, and I need to track
// them all so they can be
// disposed.
smokeA.push(mineSmokeEmitter);
}
else if ( state == “run” ){
// If the astronaut was running,
// astro die! :(
astroP.visible = false;
shakeFactor = 15;
state = “die“;
resetCounter = 120;
sndDead.play();
newCrash();
}
}
if (!mine.visible) {
mineDown = mine.getDown();
astroDown = astroP.getDown();
// This is another way to check if astro
// is in the opposite hemisphere from a
// destroyed mine.
angle =
Vector3D.angleBetween(mineDown,astroDown);
if(angle>=halfPI && angle<=threeHalvesPI){
mine.visible = true;
// If a mine is made visible,
// there is a SmokeEmitter
// instance that should be
// disposed.
for(j=smokeA.length-1;j>=0;j--){
smokeDown = smokeA[j].getDown();
angle =
Vector3D.angleBetween(smokeDown,astroDown);
429
430
Chapter 10 n 3D Game Development
if(angle>=halfPI &&
angle<=threeHalvesPI){
mineSmokeEmitter = smokeA[j];
smokeA.splice(j,1);
mineSmokeEmitter.dispose();
}
}
}
}
}
if ( Vector3D.distance( energy.getPosition(), astroPosition ) < 10
){
energyIndex = (energyIndex+1)%pointA.length;
energy.copyTransformFrom( pointA[energyIndex] );
score += 1000;
energyCount++;
if ( energyCount == 3 ){
level++;
speed += 0.2;
astro.frameSpeed += .1;
addChild(levelGUI);
levelGUI.content.tf.text = level.toString();
levelGUI.play();
energyGUI.tf.text = energyCount.toString();
energyCount = 0;
resetCounter = 160;
state = “energy”;
}
energyGUI.tf.text = energyCount.toString();
newPower();
sndReset.play();
}
sky.rotateX(0.1);
}
private function cameraF():void{
// Third-person view
Pivot3DUtils.setPositionWithReference(scene.camera, 0, 40, -30,
astroP, 0.1);
Pivot3DUtils.lookAtWithReference(scene.camera, 0, 0, 0,
astroP, astroP.getUp(), 0.2 );
// You can change to a first-person view with the
// following, but I think this game works better
// with a third-person view.
// First-person view
Version 05
//Pivot3DUtils.setPositionWithReference(scene.camera,
// 0, 15, 0, astro, .9);
//Pivot3DUtils.lookAtWithReference(scene.camera,
// 0, 11, 10, astro, astroP.getUp(), .9);
if ( shakeFactor > 0 ){
scene.camera.x += Math.random() * shakeFactor;
scene.camera.y += Math.random() * shakeFactor;
scene.camera.z += Math.random() * shakeFactor;
shakeFactor *= 0.9;
}
}
private function initGUI():void{
energyGUI = new EnergyGUI();
addChild(energyGUI);
pointsGUI = new PointsGUI();
addChild(pointsGUI);
pointsGUI.x = stage.stageWidth-pointsGUI.width;
levelGUI = new LevelGUI();
levelGUI.x = stage.stageWidth/2;
levelGUI.y = stage.stageHeight/2
}
private function updateGUI():void {
pointsGUI.tf.text = score.toString();
}
private function newPop(n:int):void{
var pos:Vector3D = astroP.getScreenCoords();
if(n>0){
var pop:MovieClip = new Pop100();
} else {
pop = new PopNeg100()
}
pop.x = pos.x;
pop.y = pos.y;
addChild( pop );
}
private function newCrash():void{
var pos:Vector3D = astroP.getScreenCoords();
var crash:Crash = new Crash();
crash.x = pos.x;
crash.y = pos.y;
addChild( crash );
}
431
432
Chapter 10 n 3D Game Development
private function newPower():void{
var pos:Vector3D = astroP.getScreenCoords();
var power:Power = new Power();
power.x = pos.x;
power.y = pos.y;
addChild( power );
}
private function cleanupF(e:Event):void{
scene.pause();
scene.removeEventListener( Scene3D.UPDATE_EVENT, updateF );
}
private function shuffle(a:Vector.<Pivot3D>) {
var i:int;
var j:int;
var e:*;
var len:int = a.length;
for (i = len-1; i>=0; i--) {
j=Math.floor((i+1)*Math.random());
e = a[i];
a[i] = a[j];
a[j] = e;
}
}
private function randomF(n1:Number,n2:Number):Number{
if(n1>n2){
return n2+(n1-n2)*Math.random();
} else {
return n1+(n2-n1)*Math.random();
}
}
}
}
Version_05 FireEmitter
package com.kglad{
import flare.core.*;
import flare.materials.*;
import flare.materials.filters.*;
public class FireEmitter extends ParticleEmiter3D {
public function FireEmitter( texture:Texture3D ) {
// Creates a ParticleMaterial3D to use with
// ParticleEmiter3D.
var material:Shader3D = new ParticleMaterial3D();
// Add filters
Version 05
material.filters.push( new TextureFilter( texture ) );
material.filters.push( new ColorParticleFilter( [ 0x334400], [ 1]) );
material.build();
// Call ParticleEmiter3D and pass the filtered material
// and a FireParticle template.
super( “fire”, material, new FireParticle() );
// Lets the particles use global coordinates instead
// of being local to this.
super.useGlobalSpace = true;
// The number of particles to emit each frame.
super.emitParticlesPerFrame = 2;
// The duration of each particle (in frames).
super.particlesLife = 50;
// The emitParticlesPerFrame is decremented by this
// amount per frame
super.decrementPerFrame = 1;
}
}
}
import flare.core.*;
import flash.geom.*;
class FireParticle extends Particle3D{
private var spin:Number;
private var velocity:Vector3D = new Vector3D();
override public function init( emitter:ParticleEmiter3D ):void {
spin = 0.1;
velocity.x = Math.random() * 0.2 - 0.1;
velocity.z = Math.random() * 0.2 - 0.1;
if ( emitter.useGlobalSpace ) velocity = emitter.localToGlobalVector(
velocity );
this.x = Math.random() * 1 - 0.5;
this.y = Math.random() * 1 - 0.5;
this.z = Math.random() * 1 - 0.5;
var scale:Number = 0.5;
this.sizeX = scale;
this.sizeY = scale;
}
override public function update(time:Number):void {
this.x += velocity.x;
this.z += velocity.z;
this.sizeX *= 0.85;
this.sizeY *= 0.85;
}
433
434
Chapter 10 n 3D Game Development
override public function clone():Particle3D {
return new FireParticle();
}
}
Version_05 SmokeEmitter
package com.kglad{
import flare.core.*;
import flare.materials.*;
import flare.materials.filters.*;
public class SmokeEmitter extends ParticleEmiter3D {
public function SmokeEmitter( texture:Texture3D ) {
// Creates a ParticleMaterial3D instance to use
// with ParticleEmiter3D.
var material:Shader3D = new ParticleMaterial3D();
// Add filters
material.filters.push( new TextureFilter( texture ) );
material.filters.push( new ColorParticleFilter( [ 0xffffff], [
0.05 ] ) );
// Call ParticleEmiter3D and pass the filtered
// material and a SmokeParticle template.
super( “smoke”, material, new SmokeParticle() );
// The number of particles to emit each frame.
super.emitParticlesPerFrame = 1;
// The duration of each particle (in frames).
super.particlesLife = 20;
}
}
}
import flare.core.*;
class SmokeParticle extends Particle3D {
private var speed:Number;
private var spin:Number;
override public function init( emitter:ParticleEmiter3D ):void {
speed = Math.random() * 5;
spin = .1;
this.x = Math.random() * 20 - 10;
this.y = Math.random() * 20 - 10;
this.z = Math.random() * 20 - 10;
var scale:Number = emitter.scaleX;
this.sizeX = scale * 2;
this.sizeY = scale * 2;
}
Version 05
override public function update(time:Number):void{
this.y += speed;
this.rotation += spin;
this.sizeX *= 1.2;
this.sizeY *= 1.2;
}
override public function clone():Particle3D {
return new SmokeParticle();
}
}
435
This page intentionally left blank
Chapter 11
Social Gaming: Social
Networks
Social gaming is game playing with some degree of social interaction with other people, including multiplayer games and games that have some relationship to a social
network. In this chapter, I’ll address some of the ways you can use the social networks Facebook, Twitter, and Google+ in your game.
Depending on the amount of functionality you want and the amount of work you are
willing to do, there are different ways to integrate your game with each of these social
networking sites. But no matter how you choose to integrate, you will need to upload
your game to a server.
Also, Facebook, Twitter, and Google+ have to contend with security issues that lead
to a multistep authentication process involving your game, the user, and the social
network. When you retrieve data from a social network, there are three participants
to consider: the user who is playing your game, your game, and your code that is
trying to access the data and the social network/data provider (Facebook, Twitter,
or Google+).
To access non-public information from Facebook, Twitter, and Google+, those data
suppliers will need the user’s login and password, as well as user permission before
your game can be authorized to access or make changes to data on the user’s behalf.
Obviously, the user shouldn’t reveal his password to you or your game, so each of
those sites has set up a multistep authentication model (implementing the OAuth
2.0 protocol) whereby your game is given a token (with permissions granted by the
user) that allows your game (limited) ability to retrieve, edit, add, and delete data on
the user’s behalf.
437
438
Chapter 11 n Social Gaming: Social Networks
To furnish your game with a token, Facebook, Twitter, and Google+ have to know
some things about your game (requiring game registration), they have to authenticate
the user (requiring the user to log in), and they need some way to determine whether
the user wants to allow your game to retrieve, add, edit, or delete (some) data on
their behalf.
At its most basic, you register your game with the data provider. Your game is then
assigned a unique identifier that identifies the game to the data provider. When you
want to access data on behalf of a user, your identifier is transmitted to the data
provider.
Facebook
The simplest way to hook into Facebook is to use their plug-ins to add a Like button,
Send button, Login button, and so on to your game. If you only want to add a Like
button to a page on your website, you can use the following iframe tag anywhere
(between the body tags) in your HTML document where you want the Facebook
Like button to appear:
<iframe src="http://www.facebook.com/plugins/like.php?href=yoursite.com/subdirectory/
page.html" scrolling="no" frameborder="0" style="border:none; width:500px; height:25px">
</iframe>
where the
example:
href
attribute is equal to the absolute URL to your HTML file. For
<iframe src="http://www.facebook.com/plugins/like.php?href=kglad.com/Files/fb/"
scrolling="no" frameborder="0" style="border:none; width:500px; height:25px">
</iframe>
points to index.html in kglad/com/Files/fb.
However, to get the most out of Facebook, you will need to use JavaScript or the
Facebook ActionScript API and register your game on the Facebook Developer App
page. Before you can register your game with Facebook, you need to be registered
with Facebook, and you need to log in.
Once you’re logged in, you can go to https://developers.facebook.com/apps and click
Create New App to register your game with Facebook (see Figure 11.1).
Facebook
Figure 11.1
Facebook’s Create New App form.
Source: Facebook® Inc.
Enter your app’s name and click Continue. Complete the security check (see Figure 11.2)
and click Submit. You should see the App Dashboard, where you enter details about
your game (see Figure 11.3).
Figure 11.2
Security Check form.
Source: Facebook® Inc.
439
440
Chapter 11 n Social Gaming: Social Networks
Figure 11.3
App Dashboard with Basic settings selected.
Source: Facebook® Inc.
If you mouse over a question mark, you will see an explanation of what is required to
complete this form. Enter a namespace (a string that allows you to group and keep
your games separate from like-named games), from the Category selection pick
Games, and then select a subcategory.
Your game can integrate with Facebook in several ways, listed above the Save
Changes button. You can select more than one, but for now select only Website
with Facebook Login and App on Facebook, enter the fully qualified URL to your
game, and click Save Changes (see Figure 11.4).
Facebook
Figure 11.4
Facebook integration menu expanded.
Source: Facebook® Inc.
You can return to https://developers.facebook.com/apps any time and edit your
entries. Your games will be listed on the left, and you can use the Edit App button
to make changes and the Create New App button to add another game.
Click on the App Center link on the left (see Figures 11.5a and 11.5b). Fill in everything that isn’t optional and upload all the required images. Again, if you mouse over
the question marks, you will see requirement details, including exact image sizes for
the icons, banners, and screenshots.
441
442
Chapter 11 n Social Gaming: Social Networks
Figure 11.5a
Top half of the App Center form.
Source: Facebook® Inc.
Facebook
Figure 11.5b
Bottom half of the App Center form.
Source: Facebook® Inc.
When you’re finished, click Save Changes and Submit App Detail Page. You should
then see Additional Notes and Terms (see Figure 11.6).
Figure 11.6
The Additional Notes and Terms page.
Source: Facebook® Inc.
443
444
Chapter 11 n Social Gaming: Social Networks
Check the check boxes and click Review Submission. If everything looks acceptable,
click Submit. You should then see something like what’s shown in Figure 11.7.
Figure 11.7
After you agree to Facebook’s terms, you should be directed back to the App Center, where you will see this
modal window.
Source: Facebook® Inc.
You are now ready to integrate your game with Facebook’s API. Because there is a
Facebook ActionScript API that communicates with Facebook’s API, you need not
work directly with the Facebook API.
But before I cover Adobe’s Facebook ActionScript API, I’m going to cover how you
can use the Facebook JavaScript API directly. There’s no reason to actually do that
while the Facebook ActionScript API still works, but by the time you read this, the
Facebook ActionScript API may no longer work.
In addition, this section shows the basics needed to use any JavaScript API, so it
applies to any social network that has a JavaScript API, including the next “hot”
social network that will arise in the future.
Facebook JavaScript API
In this section I’ll show you how you would use the Facebook JavaScript API so you
can see how to use a JavaScript API with the next hot social networking site before
Facebook
an ActionScript API is available or with Facebook if they change their cross-domain
policy file (like Twitter did).
Facebook had a REST API (http://en.wikipedia.org/wiki/Representational_state_
transfer) that they deprecated in favor of what they are calling their Graph API.
Their Graph API looks more like a name change than a change from the RESTful
principles that you can read about at the previously referenced Wikipedia page, if
you’re interested.
In any case, without diverging into theory, you can use Facebook with http requests
(http://developers.facebook.com/docs/reference/api). I’ll cover that in the “Google+”
section.
If there were no ActionScript API for Facebook, you could use the Facebook
JavaScript API to communicate between your game and Facebook. That JavaScript
should be in your SWF’s embedding HTML page. (There is a way to inject JavaScript
into the embedding HTML page using ActionScript that I will cover in the “Google+”
section.)
© 2013 Keith Gladstien, All Rights Reserved.
And you would need to use the ActionScript ExternalInterface class to communicate
between the JavaScript in your embedding HTML page and the ActionScript in your
SWF (see Figure 11.8).
Figure 11.8
Schematic diagram showing how ActionScript can communicate with Facebook. Everything on the left is in
your SWF’s embedding HTML page.
To simplify the Facebook and ActionScript communication, I will start by explaining
ActionScript and JavaScript communication (see Figure 11.9).
445
Chapter 11 n Social Gaming: Social Networks
© 2013 Keith Gladstien, All Rights Reserved.
446
Figure 11.9
Schematic diagram showing how ActionScript can communicate with JavaScript.
ActionScript and JavaScript Communication
We need ActionScript code that sends data to JavaScript and ActionScript code that
listens for data that is sent from JavaScript to ActionScript. Here is the ActionScript
code used to send the variable some_data’s value from ActionScript to JavaScript:
ExternalInterface.call("fromSWF", some_data);
Here is the ActionScript code used to listen for data that is sent from JavaScript to
ActionScript:
ExternalInterface.addCallback("swfF",this.fromJS);
function fromJS(value:String):void {
// do something with value
}
To use either of those, you need to import the
ExternalInterface
class:
import flash.external.ExternalInterface;
Both the call and addCallback methods are static methods of the ExternalInterface
class and therefore are always applied exactly as shown. The first parameter in the
call method is the JavaScript function name (always in quotes), and the second
parameter is the (optional) data you want to send to the JavaScript function. In the
context of this chapter, that second parameter should be limited to a String, an
Array, or an associative Array.
The first parameter of the addCallback method is the JavaScript function name (in
quotes), and the second parameter is an in-scope (of the addCallback code) function
name (not in quotes).
Facebook
We also need JavaScript functions fromSWF and swfF. They can be something as simple
as:
<script language="JavaScript">
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
function toSWF(value) {
thisMovie("your_swf_name_without_the_file_extension").swfF(value);
}
function fromSWF(value) {
// You can return a value from this function, and it
// will be returned to
// ExternalInterface.call("fromSWF",some_data).
return "test return from js fromSWF()";
}
</script>
The thisMovie function should not be changed. It is needed for JavaScriptto-ActionScript communication.
The toSWF function calls thisMovie and passes the name of your SWF file (without the
.swf suffix and in quotes). If you change thisMovie or fail to pass the correct SWF
name to thisMovie, JavaScript to ActionScript communication will fail.
Appended to thisMovie(swf_name) is the function name (without quotes) used in the
ActionScript addCallback method (with quotes).
In addition to all that, you need to use SWF embedding code that is compatible
with the ExternalInterface class. Here is sample embedding code that works with
ExternalInterface and embeds game_01.swf.
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="game_01"
width="500" height="375">
<param name="movie" value="game_01.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="game_01.swf" quality="high" bgcolor="#ffffff"
width="500" height="375" name="game_01" align="middle"
play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
447
448
Chapter 11 n Social Gaming: Social Networks
pluginspage="http://www.macromedia.com/go/getflashplayer">
</embed>
</object>
Notice the allowScriptAccess parameter. That parameter specifies whether the embedded SWF and the embedding HTML page can communicate. If the value is “never”,
no communication is allowed. If it is “sameDomain”, communication is allowed only
when the SWF and HTML are in the same domain. And, if the value is “always”, the
SWF and HTML file can always communicate, even if they are in different domains.
This isn’t the only code you can use to embed a SWF and work with ExternalInterface.
You can also use SWFObject (http://code.google.com/p/swfobject) to embed a SWF,
and it works well with ExternalInterface.
With both embedding methods, you only need to change the SWF name references,
stage size, and background color in the embedding code. With the above embedding
code, there are four references to game_01 that you should change to match the
name of your SWF, and you should change the width, height, and bgcolor in the
two locations to match your SWF’s width, height, and background color.
With the above embedding code for game_01, the
function toSWF(value) {
thisMovie("game_01").swfF(value);
}
toSWF
function should be:
// change game_01 to match your swf’s name
Putting all that together gives the following embedding HTML file code and the following ActionScript document class code. These files are in /support files/Chapter 11/
facebook/fb1.
Version _0a
Version _0a index.html
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html"; charset=utf-8 />
<title>game_01</title>
<script language="JavaScript">
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
Facebook
function toSWF(value) {
thisMovie("game_01").swfF(value);
}
function fromSWF(value) {
// If you have firebug installed in firefox, you can use
// console.log to display data.
console.log("ActionScript conveys: "+value+"\n");
// Otherwise, you can use the alert function.
// alert("ActionScript sends: "+value);
}
</script>
</head>
<body>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="game_01"
width="500" height="375">
<param name="movie" value="game_01.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="game_01.swf" quality="high" bgcolor="#ffffff"
width="500" height="375" name="game_01" align="middle"
play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer">
</embed>
</object>
</body>
</html>
Version _0a Main
package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.MouseEvent;
// This is needed to use ExternalInterface
import flash.external.ExternalInterface;
public class Main extends MovieClip {
private var input_tf:TextField;
private var output_tf:TextField;
private var sendBtn:Sprite;
public function Main() {
// Create an input textfield that can be used to
// test if data from the textfield can be passed
// to JavaScript.
449
450
Chapter 11 n Social Gaming: Social Networks
inputF();
// Create a send button to trigger that data send.
sendF();
// Create an output textfield that can be used to
// test if data from JavaScript is successfully
// passed to ActionScript.
outputF();
}
private function inputF():void {
input_tf = new TextField();
input_tf.type="input";
input_tf.background=true;
input_tf.border=true;
input_tf.width=360;
input_tf.height=18;
addChild(input_tf);
}
private function sendF():void {
sendBtn = new Sprite();
sendBtn.mouseEnabled=true;
sendBtn.x=input_tf.width+10;
sendBtn.graphics.beginFill(0xb0b0b0);
sendBtn.graphics.drawRect(0,0,80,20);
sendBtn.graphics.endFill();
var tf:TextField = new TextField();
tf.multiline=false;
tf.text="SEND";
tf.autoSize="left";
tf.x = (sendBtn.width-tf.width)/2;
sendBtn.addChild(tf);
sendBtn.mouseChildren=false;
sendBtn.buttonMode=true;
sendBtn.addEventListener(MouseEvent.CLICK, dataSendF);
addChild(sendBtn);
}
private function outputF():void {
output_tf = new TextField();
output_tf.y=25;
output_tf.width=450;
output_tf.height=325;
output_tf.multiline=true;
output_tf.wordWrap=true;
output_tf.border=true;
output_tf.text="";
Facebook
addChild(output_tf);
// Here is the only line of ActionScript code that
// registers a function (this.fromJS) to receive data
// from the JavaScript swfF
ExternalInterface.addCallback("swfF",this.fromJS);
}
private function dataSendF(e:MouseEvent):void {
// Here is the only line of ActionScript code that
// calls the JavaScript function fromSWF (and the
// text from input_tf is passed.
ExternalInterface.call("fromSWF",input_tf.text);
//ExternalInterface.call("notesF");
}
private function fromJS(value:String):void {
output_tf.appendText("JavaScript callback to ActionScript: " +
value + "\n");
}
}
}
Rename index_0a.html to index.html and Main_0a.as to Main.as. Then open
game_01.fla in Flash Pro and publish game_01.swf. You don’t need to publish an
HTML file from Flash, and if you do, you don’t want to use it or overwrite the
HTML file with the above code. If you have HTML ticked in the publish settings,
Flash will overwrite any same-named HTML file without warning that you are
about to overwrite a file, so check your publish settings before publishing.
You can then open index.html in your web browser and test communication from
ActionScript to JavaScript and from JavaScript to ActionScript.
The HTML document contains close to the minimum amount of code you need to
communicate between JavaScript and ActionScript.
The ActionScript in Main contains much more than the minimum amount of code
because it creates input and output textfields and a send button, all used to facilitate
testing of the JavaScript and ActionScript communication. The actual ActionScript
code needed to implement ActionScript and JavaScript communication is contained
in about six lines of code.
JavaScript and Facebook Communication
The minimum amount of code to add the Facebook JavaScript API to your embedding HTML is the following, placed just below your opening body tag. In the two
places appId appears below, you will need to change it from the App ID for
game_01 (refer to Figure 11.3) to the App ID Facebook assigned to your game
when you registered it with Facebook.
451
452
Chapter 11 n Social Gaming: Social Networks
<div id="fb-root"></div>
<script>
// Load the Facebook JavaScript API
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=229095550543776";
fjs.parentNode.insertBefore(js, fjs);
}(document, ’script’, ‘facebook-jssdk’));
// initialize Facebook JavaScript (after it loads) with your App ID
window.fbAsyncInit = function() {
FB.init({
appId
: ‘229095550543776’, // App ID
status
: true, // check login status
cookie
: true, // enable cookies to allow the server to access the session
xfbml
: true // parse XFBML
});
};
</script>
According to the Facebook documents, the fb-root div is needed. I don’t see any reason to remove it, but I also saw no problem removing it. I’m not sure what it does.
There was legacy JavaScript API loading code that appended elements to that div, but
the most recent loading code uses the script tag. Because it may be needed for some
parts of the API and for some browsers, it is probably best left as is.
You can’t do anything useful with that code alone. But starting with that code, you
can access Facebook’s JavaScript API.
Actually, Facebook calls it their JavaScript Software Development Kit (SDK), which
includes their Graph API, and their Graph API is the main JavaScript API for webbased applications. But the SDK also includes the Facebook Dialogs, such as the Feed
Dialog, Pay Dialog, and so on.
Warning: The Facebook developer documents appear to be written by different people
at different times, so there is a noticeable lack of consistency, clarity, and organization.
Nevertheless, there is a lot of useful information there.
Here is the link to their JavaScript API:
http://developers.facebook.com/docs/reference/JavaScript.
Facebook
And here is the link to the Facebook permissions page, which you’ll need to use
if you want to access anything other than public information about a user: https://
developers.facebook.com/docs/authentication/permissions.
I will explain how permissions work later. For now, open the JavaScript API reference page.
There are three main methods of the JavaScript API.
1. FB.init, which you must use to connect to the API and which I used in the previous JavaScript code.
2. FB.api, which you use to connect to Facebook’s Graph API, which taps into all
the connections that Facebook users have to everything, including other users,
events, pictures, and so on.
3. FB.ui, which you use to connect to the Facebook Dialogs.
Notice these are all static methods. In fact, the entire API uses static methods.
If you click the FB.ui link, you will see six more links, some of which show sample
JavaScript code for using the FB.ui method. I don’t understand the inconsistency, but
the code that is shown works. In a moment, I will show a list of FB.ui methods you
can use.
If you click the FB.api link, you will see another link to the Graph API and an explanation that FB.api is used to call the Graph API. There is also sample JavaScript code
that shows how to use the FB.api. This is good.
If you click the Graph API link, you are taken to a page that has no JavaScript and,
in fact, doesn’t appear to be related to the JavaScript API. Nevertheless, you can use
that page to determine the FB.api calls you can make. I will present a list of FB.api
calls after showing you how to use FB.api calls.
But it’s easier to use the FB.ui method, so that’s a good starting point. The next section covers the FB.ui or Facebook Dialog methods.
Facebook Dialogs
Because the Dialogs require explicit user interaction (and so explicit approval by the
user), there is no need to seek permissions and no need to check whether the user is
logged into Facebook. If the user is not logged in, the log-in Dialog will be presented,
followed by the specific Dialog that you called. If the user is logged in, only the called
Dialog will pop up.
453
454
Chapter 11 n Social Gaming: Social Networks
Note that you should use a mouse click to call the Dialog; otherwise, the user’s popup blocker might block the dialog pop-up. Here is the sample code to call an FB.ui
method or Facebook Dialog.
Version _0b
Version _0b index.html
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>game_01</title>
<script language="JavaScript">
function thisMovie(movieName) {
if (navigator.appName.indexOf(“Microsoft“) != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
function toSWF(value) {
thisMovie(“game_01“).swfF(value);
}
function fromSWF(value) {
console.log(“ActionScript conveys: “+value+“\n“);
// alert(“ActionScript sends: “+value);
}
// sendF is called by ActionScript ExternalInterface.call(“sendF“)
function sendF(){
// The object obj is defined below. It has 3 properties: method,
// name and link.
// The method property of FB.ui objects specifies which dialog is
// presented to the user. Using “send” triggers a dialog that
// allows users to share a link.
// The recipient receives the notice in their Message inbox.
var obj = {
method: ’send’,
name: ‘Check game_01’,
link: ‘http://www.kglad.com/Files/fb’,
}
// The first parameter in FB.ui accepts an object, and the second
// parameter is a callback function that Facebook uses to return
// (sometimes) useful data (response) after the FB.ui call.
FB.ui(obj,function(response){
toSWF(“send response**“);
// To determine what is returned, I created an evalF
Facebook
// function that iterates through the returned parameter.
evalF(response);
});
}
// If obj is an object, this function iterates through the
// object properties. Because Facebook can and does nest
// objects, this function is recursive.
function evalF(obj){
if(typeof obj == “object“){
for(var s in obj){
toSWF(s);
evalF(obj[s]);
}
} else {
toSWF(obj);
}
}
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
// load fb js sdk
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=229095550543776";
fjs.parentNode.insertBefore(js, fjs);
}(document, ’script’, ‘facebook-jssdk’));
// initialize with fb js (after it loads) with your App ID
window.fbAsyncInit = function() {
FB.init({
appId
: ‘229095550543776’, // App ID
status
: true, // check login status
cookie
: true, // enable cookies to allow the server to access the session
xfbml
: true // parse XFBML
});
};
</script>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="game_01"
width="500" height="375">
<param name="movie" value="game_01.swf" />
<param name="quality" value="high" />
455
456
Chapter 11 n Social Gaming: Social Networks
<param name="bgcolor" value="#ffffff" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="game_01.swf" quality="high" bgcolor="#ffffff"
width="500" height="375" name="game_01" align="middle"
play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer">
</embed>
</object>
</body>
</html>
Version _0b Main.as
package com.kglad{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.MouseEvent;
// This is needed to use ExternalInterface
import flash.external.ExternalInterface;
public class Main extends MovieClip {
private var input_tf:TextField;
private var output_tf:TextField;
private var sendBtn:Sprite;
public function Main() {
// Create an input textfield that can be used
// to test if data from the textfield can be
// passed to JavaScript.
inputF();
// Create a send button to trigger that data send.
sendF();
// Create an output textfield that can be used
// to test if data from JavaScript is
// successfully passed to ActionScript.
outputF();
}
private function inputF():void {
input_tf = new TextField();
input_tf.type="input";
input_tf.background=true;
input_tf.border=true;
input_tf.width=360;
input_tf.height=18;
addChild(input_tf);
Facebook
}
private function sendF():void {
sendBtn = new Sprite();
sendBtn.mouseEnabled=true;
sendBtn.x=input_tf.width+10;
sendBtn.graphics.beginFill(0xb0b0b0);
sendBtn.graphics.drawRect(0,0,80,20);
sendBtn.graphics.endFill();
var tf:TextField = new TextField();
tf.multiline=false;
tf.text="Send a Message";
tf.autoSize="left";
tf.x = (sendBtn.width-tf.width)/2;
sendBtn.addChild(tf);
sendBtn.mouseChildren=false;
sendBtn.buttonMode=true;
sendBtn.addEventListener(MouseEvent.CLICK, dataSendF);
addChild(sendBtn);
}
private function outputF():void {
output_tf = new TextField();
output_tf.y=25;
output_tf.width=450;
output_tf.height=325;
output_tf.multiline=true;
output_tf.wordWrap=true;
output_tf.border=true;
output_tf.text="";
addChild(output_tf);
ExternalInterface.addCallback("swfF",this.fromJS);
}
private function dataSendF(e:MouseEvent):void {
// Call the JavaScript function sendF, which
// calls an FB.ui method that triggers a Facebook
// Send Message pop-up.
ExternalInterface.call("sendF");
}
private function fromJS(value:String):void {
output_tf.appendText("JavaScript callback to ActionScript: " +
value + "\n");
}
}
}
457
458
Chapter 11 n Social Gaming: Social Networks
The only new things in index_0b.html are sendF and evalF. evalF is just a helper
function I added to see what Facebook is returning. sendF contains the FB.ui code
that calls the Send Message Dialog.
To use the other Facebook Dialogs, you need to make only minor changes to the
embedding HTML JavaScript and Flash ActionScript.
For example, to prompt a user to post a feed to his or her wall, you can add
between the head script tags (see index_0c.html):
feedF
function feedF(){
var obj = {
method: ‘feed’,
link: ‘http://www.kglad.com/Files/fb’,
picture: ‘http://www.kglad.com/Files/fb/game_01.jpg’,
name: ‘game_01 feed test’,
caption: ‘game_01’,
description: ‘game_01 feed dialog.’
};
FB.ui(obj, function(response){
toSWF("feed response **");
evalF(response);
});
}
And change ExternalInterface.call(“sendF”) to
com.kglad.Main_0c on the book’s website.)
ExternalInterface(“feedF”).
(See
Here is that promised list of FB.ui methods, followed by the last version of index and
Main dealing with the FB.ui methods.
// Prompt user to add a friend
function addFriendF(){
var obj = {
method: ‘friends.add’,
id: "100004023276213"
}
FB.ui(obj, function(response){
toSWF("add friend");
evalF(response);
});
}
// If your game can accept in-game payments (check the App Dashboard).
// Payments documents: http://developers.facebook.com/docs/credits/build/
// Prompt user to buy something
Facebook
function buyF() {
var obj = {
method: ‘pay’,
action: ‘buy_item’,
// You can pass any string, but your payments_get_items must
// be able to process and respond to this data.
order_info: {’item_id’: ‘1a’},
dev_purchase_params: {’oscif’: true}
};
FB.ui(obj, function(response){
toSWF("buy");
evalF(response);
});
}
// Post to feed
function feedF(){
var obj = {
method: ‘feed’,
link: ‘http://www.kglad.com/Files/fb’,
picture: ‘http://www.kglad.com/Files/fb/game_01.jpg’,
name: ‘game_01 feed test’,
caption: ‘game_01’,
description: ‘game_01 feed dialog.’
};
FB.ui(obj, function(response){
toSWF("feed response **");
evalF(response);
});
}
// If something fails silently, you need to request permission
// Request permissions for non-public data
function permissionsF(){
var obj = {
method: ‘permissions.request’,
perms: ‘user_birthday,user_relationship_details,read_stream’,
display: ‘popup’
};
FB.ui(obj, function(response) {
toSWF("permissions **");
evalF(response);
});
}
// Send a request from the current user to one or more recipients
function requestF(){
459
460
Chapter 11 n Social Gaming: Social Networks
var obj = {
method: ‘apprequests’,
message: ‘Join Me!’
}
FB.ui(obj, function(response){
toSWF("request reponse **");
evalF(response);
});
}
// Share a link
function sendF(){
var obj = {
method: ’send’,
name: ‘Check game_01’,
link: ‘http://www.kglad.com/Files/fb’,
}
FB.ui(obj,function(response){
toSWF("send response**");
evalF(response);
});
}
Version _0d
version _0d index.html
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>game_01</title>
<script language="JavaScript">
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
function toSWF(value) {
thisMovie("game_01").swfF(value);
}
function fromSWF(value) {
// If you have firebug installed in firefox, you can use
// console.log to display data.
console.log("ActionScript conveys: "+value+"\n");
Facebook
// Otherwise, you can use the alert function.
// alert("ActionScript sends: "+value);
}
// Prompt user to add a friend
function addFriendF(){
var obj = {
method: ‘friends.add’,
id: 123456789
}
FB.ui(obj, function(response){
toSWF("add friend");
evalF(response);
});
}
// If your game can accept in-game payments (check the App Dashboard):
// http://developers.facebook.com/docs/credits/build/
function buyF() {
var obj = {
method: ‘pay’,
action: ‘buy_item’,
// You can pass any string, but your payments_get_items must
// be able to process and respond to this data.
order_info: {’item_id’: ‘1a’},
dev_purchase_params: {’oscif’: true}
};
FB.ui(obj, function(response){
toSWF("buy");
evalF(response);
});
}
// Post to feed
function feedF(){
var obj = {
method: ‘feed’,
link: ‘http://www.kglad.com/Files/fb’,
picture: ‘http://www.kglad.com/Files/fb/game_01.jpg’,
name: ‘game_01 feed test’,
caption: ‘game_01’,
description: ‘game_01 feed dialog.’
};
FB.ui(obj, function(response){
toSWF("feed response **");
evalF(response);
461
462
Chapter 11 n Social Gaming: Social Networks
});
}
// Request permissions for non-public data
// https://developers.facebook.com/docs/authentication/permissions/
function permissionsF(){
var obj = {
method: ‘permissions.request’,
perms: ‘user_birthday,user_relationship_details,read_stream’,
display: ‘popup’
};
FB.ui(obj, function(response) {
toSWF("permissions **");
evalF(response);
});
}
// Send a request from the current user to one or more recipients
function requestF(){
var obj = {
method: ‘apprequests’,
message: ‘Join Me!’
}
FB.ui(obj, function(response){
toSWF("request reponse **");
evalF(response);
});
}
// sendF is called by ActionScript ExternalInterface.call("sendF")
function sendF(){
// The object obj is defined below. It has 3 properties:
// method, name, and link.
// The method property of FB.ui objects specifies which dialog
// is presented to the user. Using "send" triggers a dialog that
// allows users to share a link.
// The recipient receives the notice in their Message inbox.
var obj = {
method: "send",
name: ‘Check game_01’,
link: ‘http://www.kglad.com/Files/fb’,
}
// The first parameter in FB.ui accepts an object, and the second
// parameter is a callback function that Facebook uses to return
// (sometimes) useful data (response) after the FB.ui call.
FB.ui(obj,function(response){
toSWF("send response**");
Facebook
// To determine what is returned, I created an evalF
// function that iterates through the returned parameter.
evalF(response);
});
}
// If obj is an object, this function iterates through the
// object properties.
// Because Facebook can and does nest objects, this function
// is recursive.
function evalF(obj){
if(typeof obj == "object"){
for(var s in obj){
toSWF(s);
evalF(obj[s]);
}
} else {
toSWF(obj);
}
}
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
// load fb js sdk
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=229095550543776";
fjs.parentNode.insertBefore(js, fjs);
}(document, ’script’, ‘facebook-jssdk’));
// initialize with fb js (after it loads) with your App ID
window.fbAsyncInit = function() {
FB.init({
appId
: ‘229095550543776’, // App ID
status
: true, // check login status
cookie
: true, // enable cookies to allow the server to access the session
xfbml
: true // parse XFBML
});
};
</script>
463
464
Chapter 11 n Social Gaming: Social Networks
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="game_01"
width="500" height="375">
<param name="movie" value="game_01.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="game_01.swf" quality="high" bgcolor="#ffffff"
width="500" height="375" name="game_01" align="middle"
play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer">
</embed>
</object>
</body>
</html>
Version _0d Main.as
package com.kglad{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.MouseEvent;
// This is needed to use ExternalInterface
import flash.external.ExternalInterface;
public class Main extends MovieClip {
private var output_tf:TextField;
private var btn:Sprite;
private var tf:TextField;
private var returnS:String;
private var btnA:Array = ["Add
Friend","Buy","Feed","Permissions","Request","Send"];
private var nextX:int = 0;
private var gapX:int = 5;
public function Main() {
for(var i:int=0;i<btnA.length;i++){
buttonF(i);
}
outputF();
}
private function buttonF(i:int):void {
tf = new TextField();
with(tf){
multiline=false;
text=btnA[i];
Facebook
autoSize="left";
}
btn = new Sprite();
with(btn){
name = i.toString();
mouseEnabled=true;
mouseChildren = false;
buttonMode = true;
x=nextX;
graphics.beginFill(0xb0b0b0);
graphics.drawRect(0,0,tf.width+10,20);
graphics.endFill();
}
nextX += btn.width+gapX;
tf.x = (btn.width-tf.width)/2;
btn.addChild(tf);
btn.addEventListener(MouseEvent.CLICK, dataSendF);
addChild(btn);
}
private function outputF():void {
output_tf = new TextField();
output_tf.y=25;
output_tf.width=450;
output_tf.height=325;
output_tf.multiline=true;
output_tf.wordWrap=true;
output_tf.border=true;
output_tf.text="";
addChild(output_tf);
ExternalInterface.addCallback("swfF",this.fromJS);
}
private function dataSendF(e:MouseEvent):void {
// stringF() just converts the strings in btnA
// to match the corresponding JavaScript function
// (as a string).
ExternalInterface.call(stringF(btnA[int(e.currentTarget.
name)]));
}
private function stringF(s:String):String{
returnS = s.split(" ").join("")+"F";
return returnS.substr(0,1).toLowerCase()+returnS.substr(1);
}
private function fromJS(value:String):void {
465
466
Chapter 11 n Social Gaming: Social Networks
output_tf.appendText("JavaScript callback to ActionScript: " +
value + "\n");
}
}
}
I hard-coded several things that will not be typically hard-coded in your game. For
example, permissionsF will almost certainly not have the perms property hard-coded.
You will pass a permissionS string to permissionsF specifying the permissions you need
and using the strings listed on https://developers.facebook.com/docs/authentication/
permissions.
To access non-public data, you will need to explicitly obtain the user’s permission for
each and every non-public datum you want to access. Because most users will (or at
least should) be reluctant to grant those permissions without some explanation and
understanding of what you will do with that data, you should minimize your
requests, explain them, and only make a request when it will benefit the user. Otherwise, you’ll get many denied requests.
So, it is better to call permissionsF several times, requesting one permission each time
so you can quickly explain to the user the benefit of granting that specific permission.
// Request permissions for non-public data
function permissionsF(permissionS){
var obj = {
method: ‘permissions.request’,
perms: permissionS,
display: ‘popup’
};
FB.ui(obj, function(response) {
toSWF("permissions **");
evalF(response);
});
}
Facebook Social Graph
You will use the FB.api methods to access the Facebook social graph, which consists
of Facebook users and all their connections to other users (their friends), events,
music, photos, and so on.
If you have obtained the appropriate permissions, the data returned from Facebook
to your game isn’t noticeable by the user. That is, there are no pop-ups if the user is
logged in and no new permissions are needed, and it’s not necessary to have the user
click a button to trigger queries to Facebook.
Facebook
When a user links to your game, you have to check whether the user is logged in. If
he is not and your game uses Facebook data, you need to offer a log-in button. I’ll
show you how to do that below, after I show you how to check whether the user is
logged in.
You should do that check when the Facebook JavaScript API is initialized, which
occurs when window.fbAsyncInit executes. Here is sample code:
window.fbAsyncInit = function() {
FB.init({
appId
: ‘229095550543776’, // App ID
status
: true, // check login status
cookie
: true, // enable cookies to allow the server to access the session
xfbml
: true // parse XFBML
});
// fbLoginStatus is called when there is a login status change
// and when fb js initializes
function fbLoginStatus(response) {
// If the user is logged in, response.status is ‘connected’
if (response.status === ‘connected’) {
FB.api(’/me’, function(response) {
toSWF("is connected:: "+response.status);
evalF(response);
});
} else {
// The user is not logged in.
toSWF("not connected:: "+response.status);
evalF(response);
}
}
// This is the code that calls fbLoginStatus on initialization
FB.getLoginStatus(fbLoginStatus);
// This is the code that calls fbLoginStatus when there is a status change
FB.Event.subscribe(’auth.statusChange’, fbLoginStatus);
};
The log-in status is communicated to Main (via toSWF), and that information is used
to determine whether to present the user with a log-in button. We also use that
information to determine whether to display the other social graph buttons.
Those buttons to check for friends and so on make sense for the application we made
to show how the FB.api methods work, but having buttons for some or all those
things may make no sense in your game. There will probably be other game events
triggering a query of the user’s friends, for example.
467
468
Chapter 11 n Social Gaming: Social Networks
The general form of an FB.api method is:
function friendsF(){
FB.api(’/me/friends’, function(response) {
toSWF("Friends **");
evalF(response);
});
}
where calling friendsF queries Facebook about the user’s friends. When testing
version _01, you can see what is returned from Facebook for the 10 or so FB.api
methods I included. You can use the mouse wheel to scroll output_tf so you can
see all the text returned by Facebook.
In the version _01 index.html code, notice the FB.login method accepts two parameters. The first is the callback function, and the second is an optional object with
a scope property where you can specify some of the permissions you need when a
user logs in. I included four permissions in the sample code, but that would probably
cause most users to deny the permissions request. I think it would be best not to
request permissions at login.
Version _01
Version _01 index.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>game_01</title>
<script language="JavaScript">
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
function toSWF(value) {
thisMovie("game_01").swfF(value);
}
function fromSWF(value) {
// If you have firebug installed in firefox, you can use
// console.log to display data.
Facebook
console.log("ActionScript conveys: "+value+"\n");
// Otherwise, you can use the alert function.
// alert("ActionScript sends: "+value);
}
/////////////////////
// FB.api - graph API
/////////////////////
// Albums
function albumsF(){
FB.api(’/me/albums’, function(response) {
toSWF(";Albums**");
evalF(response);
});
}
// Books
function booksF(){
FB.api(’/me/books’, function(response) {
toSWF("Books **");
evalF(response);
});
}
// Check-ins
function checkInsF(){
FB.api(’/me/checkins’, function(response) {
toSWF("Check-ins **");
evalF(response);
});
}
// Events
function eventsF(){
FB.api(’/me/events’, function(response) {
toSWF("Events **");
evalF(response);
});
}
// Feed
function checkFeedF(){
FB.api(’/me/feed’, function(response) {
toSWF("Checked Feed **");
evalF(response);
});
}
// Friends
function friendsF(){
469
470
Chapter 11 n Social Gaming: Social Networks
FB.api(’/me/friends’, function(response) {
toSWF("Friends **");
evalF(response);
});
}
// Groups
function groupsF(){
FB.api(’/me/groups’, function(response) {
toSWF("Groups **");
evalF(response);
});
}
// Likes
function likesF(){
FB.api(’/me/likes’, function(response) {
toSWF("Likes **");
evalF(response);
});
}
// Locations
function locationsF(){
FB.api(’/me/locations’, function(response) {
toSWF("Locations **");
evalF(response);
});
}
// Log in
function logInF(){
FB.login(function(response) {
toSWF("log in **");
evalF(response);
if (response.authResponse) {
toSWF("user is logged in and granted some permissions.");
} else {
toSWF("User cancelled login or did not fully authorize.");
}
}, {scope:’read_stream,publish_stream,user_events,user_notes’});
}
// Movies
function moviesF(){
FB.api(’/me/movies’, function(response) {
toSWF("Movies **");
evalF(response);
});
}
Facebook
// Notes
function notesF(){
FB.api(’/me/notes’, function(response) {
toSWF("NOTES **");
evalF(response);
});
}
// Permissions
function checkPermissionsF(){
FB.api(’/me/permissions’, function(response) {
toSWF("Checked permissions **");
evalF(response);
});
}
// Photos
function photosF(){
FB.api(’/me/photos’, function(response) {
toSWF("Photos **");
evalF(response);
});
}
// Videos
function videosF(){
FB.api(’/me/videos’, function(response) {
toSWF("Videos **");
evalF(response);
});
}
// Video uploaded
function videoUploadedF(){
FB.api(’/me/uploaded’, function(response) {
toSWF("Video uploaded **");
evalF(response);
});
}
//////////////////////////////////
// FB.ui - Facebook dialogs follow
//////////////////////////////////
// Prompt user to add a friend
function addFriendF(){
var obj = {
method: ‘friends.add’,
id: "100004023276213"
}
471
472
Chapter 11 n Social Gaming: Social Networks
FB.ui(obj, function(response){
toSWF("add friend");
evalF(response);
});
}
// If your game can accept in-game payments (check the App Dashboard).
// Payments documents: http://developers.facebook.com/docs/credits/build/
// Prompt user to buy something
function buyF() {
var obj = {
method: ‘pay’,
action: ‘buy_item’,
// You can pass any string, but your payments_get_items must
// be able to process and respond to this data.
order_info: {’item_id’: ‘1a’},
dev_purchase_params: {’oscif’: true}
};
FB.ui(obj, function(response){
toSWF("buy");
evalF(response);
});
}
// Post to feed
function feedF(){
var obj = {
method: ‘feed’,
link: ‘http://www.kglad.com/Files/fb’,
picture: ‘http://www.kglad.com/Files/fb/game_01.jpg’,
name: ‘game_01 feed test’,
caption: ‘game_01’,
description: ‘game_01 feed dialog.’
};
FB.ui(obj, function(response){
toSWF("feed response **");
evalF(response);
});
}
// If something fails silently, you need to request permission
// Request permissions for non-public data
function permissionsF(){
var obj = {
method: ‘permissions.request’,
perms: ‘user_birthday,user_relationship_details,read_stream’,
Facebook
display: ‘popup’
};
FB.ui(obj, function(response) {
toSWF("permissions **");
evalF(response);
});
}
// Send a request from the current user to one or more recipients
function requestF(){
var obj = {
method: ‘apprequests’,
message: ‘Join Me!’
}
FB.ui(obj, function(response){
toSWF("request response **");
evalF(response);
});
}
// sendF is called by ActionScript ExternalInterface.call("sendF")
function sendF(){
// The object obj is defined below. It has 3 properties: method,
// name, and link.
// The method property of FB.ui objects specifies which dialog is
// presented to the user. Using "send" triggers a dialog that
// allows users to share a link.
// The recipient receives the notice in their Message inbox.
var obj = {
method: ’send’,
name: ‘Check game_01’,
link: ‘http://www.kglad.com/Files/fb’,
}
// The first parameter in FB.ui accepts an object, and the second
// parameter is a callback function that Facebook uses to return
// (sometimes) useful data (response) after the FB.ui call.
FB.ui(obj,function(response){
toSWF("send response**");
// To determine what is returned, I created an evalF function
// that iterates through the returned parameter.
evalF(response);
});
}
function evalF(obj){
if(typeof obj == "object"){
for(var s in obj){
473
474
Chapter 11 n Social Gaming: Social Networks
toSWF(s);
evalF(obj[s]);
}
}
else {
toSWF(obj);
}
}
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
// load fb js sdk
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=229095550543776";
fjs.parentNode.insertBefore(js, fjs);
}(document, ’script’, ‘facebook-jssdk’));
// initialize with fb js (after it loads) with your App ID
window.fbAsyncInit = function() {
FB.init({
appId
: ‘229095550543776’, // App ID
status
: true, // check login status
cookie
: true, // enable cookies to allow the server to access the session
xfbml
: true // parse XFBML
});
// fbLoginStatus is called when there is a login status change and
// when fb js initializes
function fbLoginStatus(response) {
// If the user is logged in, response.status is ‘connected’
if (response.status === ‘connected’) {
FB.api(’/me’, function(response) {
toSWF("is connected:: "+response.status);
evalF(response);
});
} else {
// The user is not logged in.
toSWF("not connected:: "+response.status);
evalF(response);
}
}
Facebook
// This is the code that calls fbLoginStatus on initialization
FB.getLoginStatus(fbLoginStatus);
// This is the code that calls fbLoginStatus when there is
// a status change
FB.Event.subscribe(’auth.statusChange’, fbLoginStatus);
};
</script>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="game_01"
width="650" height="400">
<param name="movie" value="game_01.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="game_01.swf" quality="high" bgcolor="#ffffff"
width="650" height="400" name="game_01" align="middle"
play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer">
</embed>
</object>
</body>
</html>
Version _01 Main.as
package com.kglad{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.MouseEvent;
// This is needed to use ExternalInterface
import flash.external.ExternalInterface;
public class Main extends MovieClip {
private var output_tf:TextField;
private var btn:Sprite;
private var tf:TextField;
private var returnS:String;
private var btnA:Array = ["Add
Friend","Buy","Feed","Permissions","Request","Send","Albums","Books","Check
Ins","Events","Check
Feed","Friends","Groups","Likes","Locations","Movies","Notes","Check
Permissions","Photos","Videos","Video Uploaded","Log In"];
private var nextX:int = 0;
private var nextY:int = 0;
475
476
Chapter 11 n Social Gaming: Social Networks
private var gapX:int = 5;
public function Main() {
for(var i:int=0;i<btnA.length;i++){
buttonF(i);
}
getChildByName(String(btnA.length-1)).visible = false;
outputF();
}
private function loggedInF(b:Boolean):void{
// Hides the login button if the user is logged in
// and hides the social graph buttons if the user
// is not logged in.
getChildByName(String(btnA.length-1)).visible = !b ;
for(var i:int=6;i<btnA.length-1;i++){
getChildByName(String(i)).visible = b;
}
}
private function buttonF(i:int):void {
tf = new TextField();
with(tf){
multiline=false;
text=btnA[i];
autoSize="left";
}
btn = new Sprite();
addChild(btn);
with(btn){
name = i.toString();
mouseEnabled=true;
mouseChildren = false;
buttonMode = true;
x=nextX;
y=nextY;
graphics.beginFill(0xb0b0b0);
graphics.drawRect(0,0,tf.width+10,20);
graphics.endFill();
}
if(i==6 || btn.x+btn.width>stage.stageWidth){
btn.x = 0;
nextY += 25;
btn.y = nextY;
nextX = btn.x+btn.width+gapX;
} else {
nextX += btn.width+gapX;
Facebook
}
tf.x = (btn.width-tf.width)/2;
btn.addChild(tf);
btn.addEventListener(MouseEvent.CLICK, dataSendF);
}
private function outputF():void {
output_tf = new TextField();
output_tf.y=nextY+25;
output_tf.width=stage.stageWidth-10;
output_tf.height=stage.stageHeight-output_tf.y-10;
output_tf.multiline=true;
output_tf.wordWrap=true;
output_tf.border=true;
output_tf.text="";
addChild(output_tf);
ExternalInterface.addCallback("swfF",this.fromJS);
}
private function dataSendF(e:MouseEvent):void {
// stringF() just converts the strings in btnA
// to match the corresponding JavaScript function
// (as a string).
ExternalInterface.call(stringF(btnA[int(e.currentTarget.name)]));
}
private function stringF(s:String):String{
returnS = s.split(" ").join("")+"F";
return returnS.substr(0,1).toLowerCase()+returnS.substr(1);
}
private function fromJS(value:String):void {
if(value.indexOf("not connected::")>-1){
loggedInF(false);
} else if(value.indexOf(" is connected::")>-1){
loggedInF(true);
}
output_tf.appendText("JS callback to AS:" + value + ":\n");
if(output_tf.maxScrollV>1){
output_tf.scrollV = output_tf.maxScrollV;
}
}
}
}
477
478
Chapter 11 n Social Gaming: Social Networks
Adobe’s Facebook ActionScript API
The Adobe Facebook ActionScript API also uses the ExternalInterface class and
JavaScript to communicate with Facebook, so you still need to use SWF embedding
code that is compatible with the ExternalInterface class. Adobe uses SWFObject
(http://code.google.com/p/swfobject) to embed the SWF and injects the JavaScript
using XML and ActionScript. You can check the FacebookJSBridge class to see how
that is done.
At the time of this writing, the Adobe Facebook ActionScript (version 1.8.1) code
uses Mike Chambers’ JSON class. But if you are publishing for Flash Player 11 or
better, you must use the native Flash JSON class.
Both JSON classes are similar in that they are named the same and use static functions to convert to and from JSON format. Where Chambers’ JSON class uses
JSON.decode() and JSON.encode(), the native Flash JSON class uses JSON.parse() and
JSON.stringify().
So, if you use Adobe’s Facebook ActionScript API and see:
1061: Call to a possibly undefined method encode through a reference with static type
Class.
errors, you’ll need to replace
JSON.stringify().
JSON.decode()
with
JSON.parse
and
JSON.encode()
with
To use Adobe’s Facebook ActionScript API, navigate to http://code.google.com/p/
facebook-actionscript-api and download the latest version of the GraphAPI documentation, examples, source code, and/or SWC files.
If you’re publishing for Flash Player 11 or better, you will need to change all the
JSON methods in the non-SWC files. If you use the SWC files instead of the source
files, you will have less code to change.
In /support files/Chapter 11/facebook/fb2 are files that use Adobe’s Facebook
ActionScript API for the web. The code that uses the API is in FlashWebMain.as.
With only a few changes, FlashWebMain.as (the document class of
FlashWebExample.fla) is the same as Adobe’s example file with the same name. I
added details (in cbF, dialogsF, and graphF) showing how to use the Facebook.ui and
Facebook.api methods.
FlashWebMain.as
/*
Copyright (c) 2010, Adobe Systems Incorporated
All rights reserved.
Facebook
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions, and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package {
import
import
import
import
import
import
public
com.adobe.serialization.json.JSON;
com.facebook.graph.Facebook;
flash.display.Sprite;
flash.events.MouseEvent;
fl.data.DataProvider;
flash.events.Event;
class FlashWebMain extends Sprite {
protected static const APP_ID:String = "229095550543776"; //Place your
application id here
public function FlashWebMain() {
configUI();
cbF();
}
protected function configUI():void {
//listeners for UI
479
480
Chapter 11 n Social Gaming: Social Networks
loginToggleBtn.addEventListener(MouseEvent.CLICK,
handleLoginClick, false, 0, true);
clearBtn.addEventListener(MouseEvent.CLICK, handleClearClick,
false, 0, true);
//Initialize Facebook library
Facebook.init(APP_ID, onInit);
}
protected function onInit(result:Object, fail:Object):void {
if (result) { //already logged in because of existing session
outputTxt.text = "onInit, Logged In\n";
loginToggleBtn.label = "Log Out";
loginout_tf.text = "FB.logout";
} else {
outputTxt.text = "onInit, Not Logged In\n";
}
}
protected function handleLoginClick(event:MouseEvent):void {
if (loginToggleBtn.label == "Log In") {
var opts:Object = {scope:"publish_stream, user_photos"};
Facebook.login(onLogin, opts);
} else {
Facebook.logout(onLogout);
}
}
protected function onLogin(result:Object, fail:Object):void {
if (result) { //successfully logged in
outputTxt.text = "Logged In";
loginToggleBtn.label = "Log Out";
loginout_tf.text = "FB.logout";
} else {
outputTxt.appendText("Login Failed\n");
}
}
protected function onLogout(success:Boolean):void {
outputTxt.text = "Logged Out";
loginToggleBtn.label = "Log In";
loginout_tf.text = "FB.login";
}
protected function onUICallback(result:Object):void {
outputTxt.appendText("\n\nUICallback: " +
JSON.stringify(result));
Facebook
outputTxt.verticalScrollPosition
outputTxt.maxVerticalScrollPosition ;
}
=
protected function onCallApi(result:Object, fail:Object):void {
if (result) {
outputTxt.appendText("\n\nRESULT:\n" +
JSON.stringify(result));
} else {
outputTxt.appendText("\n\nFAIL:\n" +
JSON.stringify(fail));
}
outputTxt.verticalScrollPosition =
outputTxt.maxVerticalScrollPosition ;
}
protected function handleClearClick(event:MouseEvent):void {
outputTxt.text = "";
}
private function cbF():void{
// dialogs_cb
var dp:DataProvider = new DataProvider();
var obj:Object = {id:"100004023276213"};
dp.addItem({label:"Add
Friend",data:{dialog:"friends.add",params:obj}});
obj = {
action: ‘buy_item’,
order_info: {’item_id’: ‘1a’},
dev_purchase_params: {’oscif’: true}
};
dp.addItem({label:"Buy",data:{dialog:"pay",params:obj}});
obj = {
link: ‘http://www.kglad.com/Files/fb’,
picture: ‘http://www.kglad.com/Files/fb/game_01.jpg’,
name: ‘game_01 feed test’,
caption: ‘game_01’,
description: ‘game_01 feed dialog.’
}
dp.addItem({label:"Feed",data:{dialog:"feed",params:obj}});
obj = {
perms: ‘user_birthday,user_relationship_details,read_stream’,
display: ‘popup’
};
481
482
Chapter 11 n Social Gaming: Social Networks
dp.addItem({label:"Permissions",data:{dialog:"permissions.request",
params:obj}});
obj = {
message: ‘Join Me!’
}
dp.addItem({label:"Request",data:{dialog:"apprequests",params:obj}});
dialogs_cb.dataProvider = dp;
dialogs_cb.addEventListener(Event.CHANGE,dialogsF);
// graph_cb
dp = new DataProvider();
var graphA:Array =
["Albums ","Books","Checkins","Events", "Feed","Friends","Groups","Likes ",
"Locations","Movies","Notes","Permissions","Photos","Videos","Uploaded"];
for(var i:int=0;i<graphA.length;i++){
dp.addItem({label:graphA[i],data:graphA[i].toLowerCase()});
}
graph_cb.dataProvider = dp;
graph_cb.addEventListener(Event.CHANGE,graphF);
}
private function dialogsF(e:Event):void{
var data:Object = dialogs_cb.selectedItem.data
Facebook.ui(data.dialog, data.params, onUICallback);
}
private function graphF(e:Event):void{
var data:Object = graph_cb.selectedItem;
Facebook.api("/me/"+graph_cb.selectedItem.data, onCallApi, {},
"GET");
}
}
}
Twitter
There are three levels of Twitter features you can add to your game. The amount of
work required to add features from these levels increases as the feature level
increases.
The first and easiest feature level is adding one (or more) of four Twitter buttons to
your embedding HTML (see Figure 11.10).
Twitter
Figure 11.10
Collection of Twitter buttons that can be easily added to your embedding HTML.
Source: Twitter® Inc.
If you only need to use one or more of those buttons, you simply need to read the
upcoming “Adding Twitter Buttons” section.
The second and third feature levels require use of Twitter’s REST API (https://
dev.twitter.com/docs/api). The difference between what I call the second and third
levels is that second-level REST API requests require no authentication, while thirdlevel requests require authentication.
To determine whether you need to use authentication, check Twitter’s REST API. It
looks as if there are fewer than 150 resource requests you can make from Twitter at
the time of this writing, so it’s easy to scan the list to see what requests you need for
your game.
Click on each request you need to see details of the request. Toward the top on the
right side is a Resource Information box that contains a Requires Authentication?
listing (see Figure 11.11).
Resource information
box with Requires
Authentication?
listing, among others
Figure 11.11
Each Twitter request links to a page like this, showing details about that request.
Source: Twitter® Inc.
483
484
Chapter 11 n Social Gaming: Social Networks
If it says Yes, you need to use at least one level-three feature, and you should read the
“Requests That Require Authentication” section. If it says No or Supported, authentication is not needed for that request.
If none of your requests need authentication, you should read the “Requests That Do
Not Require Authentication” section.
Because there is a significant difference in complexity in implementing Twitter integration among the three levels, you probably want to use the lowest level that meets
your needs.
Adding Twitter Buttons
If you go to https://twitter.com/about/resources/buttons, you can pick one (or more)
of those four buttons and generate the code needed to add them to your embedding
HTML. Just follow the directions and copy and paste the rendered code into your
HTML document.
Share a Link Button
For example, to add a Tweet button (see Share a Link in Figure 11.10) that links to
http://www.kglad.com/Files/twitter and tweets “Check this”, you would use the
following:
<a href="https://twitter.com/share" class="twitter-share-button" dataurl="http://www.kglad.com/Files/twitter" data-text="Check this".>Tweet</a>
<script>
!function(d,s,id){
var js,fjs=d.getElementsByTagName(s)[0];
if(!d.getElementById(id)){
js=d.createElement(s);
js.id=id;
js.src="//platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}
}(document,"script","twitter-wjs");
</script>
You should change the data-url and data-text attributes to strings appropriate for
your game. You can experiment with the optional settings to see what they do.
Add your Twitter button code to your embedding HTML document, positioning
your button to suit your needs.
Twitter
Follow Button
<a href="https://twitter.com/kglad" class="twitter-follow-button" datashow-count="false">Follow @kglad</a>
<script>
!function(d,s,id){
var js,fjs=d.getElementsByTagName(s)[0];
if(!d.getElementById(id)){
js=d.createElement(s);
js.id=id;
js.src="//platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}
}(document,"script","twitter-wjs");
</script>
Hashtag Button
<a href="https://twitter.com/intent/tweet?button_hashtag=TwitterStories&text=kglad
%20story" class="twitter-hashtag-button" data-related="kglad" data-url="http://www.
kglad.com/Files/twitter">Tweet #TwitterStories</a>
<script>
!function(d,s,id){
var js,fjs=d.getElementsByTagName(s)[0];
if(!d.getElementById(id)){
js=d.createElement(s);
js.id=id;
js.src="//platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}
}(document,"script","twitter-wjs");
</script>
Mention Button
<a href="https://twitter.com/intent/tweet?screen_name=kglad" class="twitter-mentionbutton" data-related="kglad">Tweet to @kglad</a>
<script>
!function(d,s,id){
var js,fjs=d.getElementsByTagName(s)[0];
if(!d.getElementById(id)){
js=d.createElement(s);
js.id=id;
js.src="//platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}
485
486
Chapter 11 n Social Gaming: Social Networks
}(document,"script","twitter-wjs");
</script>
Notice that the JavaScript for each button is identical. If you use more than one of
these buttons, you need to add the JavaScript only once.
Requests That Do Not Require Authentication
If you want to integrate your game with Twitter, you can use the Twitter REST API
at https://dev.twitter.com/docs/api.
Many requests do not require authentication. These requests are easier to use than
those requiring authentication. Just click on a link on https://dev.twitter.com/docs/
api, and toward the bottom, after an explanation of the parameters, is an https GET
request that you can copy. Just change, add, or remove parameters to suit your
needs and use that with a URLLoader.
However, because of cross-domain security issues, you won’t be able to load the
responses from twitter.com directly into your SWF. A cross-domain security file at
twitter.com would remove the issue.
In 2009, Twitter had a cross-domain file but removed it because of security issues. It
doesn’t seem likely that they will add one again soon.
You can circumvent the problem by having your Flash game call a same-domain executable (such as a PHP) file that does the cross-domain querying of Twitter and then
returns the Twitter response to your game. That’s what I did in the /support files/
Chapter 11/twitter/no_authentication files. I used libcurl, a PHP library that allows
http and https connection protocols, in gateway.php.
gateway.php
<?php
$requestURL = $_POST[’requestURL’];
$cr = curl_init($requestURL);
curl_setopt($cr, CURLOPT_RETURNTRANSFER, true);
$returnS = curl_exec($cr);
curl_close($cr);
echo $returnS;
?>
You don’t need to understand PHP to use this file, because nothing in it needs to be
changed. Just upload it to your server in the same directory with your game SWF and
its embedding HTML file.
Twitter
All the Twitter responses can be returned in at least two formats: JSON (JavaScript
Object Notation) and XML. I used JSON for no particular reason. You can use either.
Requests are made using an http or https call to twitter.com with the format specified
in the URL. For example, to query five favorites for kglad in JSON, use the following:
https://api.twitter.com/1/favorites.json?count=5&screen_name=kglad
The same query but requesting the return in XML would be the following:
https://api.twitter.com/1/favorites.xml?count=5&screen_name=kglad
Notice the only difference is the suffix after
specified.
favorites
where JSON and XML are
If you are publishing for Flash Player 11 or better, you can use the native Flash JSON
class. Otherwise, you can use Mike Chambers’ JSON class, which you can download
from https://github.com/mikechambers/as3corelib.
Extract the files and either add the as3corelib.swc file to your library path or copy his
com directory and files from the src directory to your game’s directory and import
his JSON class.
To add his SWC file to your library, click File > Publish Settings and click the
ActionScript Settings icon (refer to Figure 10.1). Select the Library Path tab and
click the Browse to SWC File icon (refer to Figure 10.2). Navigate to the
as3corelib.swc file and click OK and then OK again.
Or, if you copy his com directory to your game’s development directory, import his
JSON class:
import com.adobe.serialization.json.JSON;
The code used to handle JSON data is slightly different depending on whether you
use the native Flash JSON class or Mike Chambers’ JSON class. I will show both in
the following Main.as code.
Main is the document class for twitter.swf in /support files/Chapter 11/twitter/
no_authentication. If you open twitter.fla from the directory, you will see an input
textfield (screenName_tf), a combobox (cb), and a textfield to show the return from
Twitter (return_tf).
Main.as
package com.kglad{
import flash.display.MovieClip;
import flash.net.URLLoader;
import flash.events.Event;
import flash.net.URLRequest;
487
488
Chapter 11 n Social Gaming: Social Networks
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.events.IOErrorEvent;
// If you are publishing for less than Flash Player 11, use
// Mike Chambers’ JSON class. Otherwise, use the native JSON
// class https://github.com/mikechambers/as3corelib
//import com.adobe.serialization.json.JSON;
public class Main extends MovieClip {
private var urlVar:URLVariables;
private var urlReq:URLRequest;
private var urlLoader:URLLoader;
public function Main() {
return_tf.text = "";
// Default Twitter screen name.
screenName_tf.text = "kglad";
// Set up the combobox
requestsF();
urlVar = new URLVariables();
// php file that forwards the request from the swf to
// Twitter and returns Twitter’s response to the swf.
urlReq=new URLRequest("gateway.php");
urlReq.method=URLRequestMethod.POST;
urlLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE,completeF);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR,errorF);
}
private function loadF(urlS:String):void {
urlVar.requestURL=urlS;
urlReq.data=urlVar;
urlLoader.load(urlReq);
}
private function requestsF():void {
// Combobox items added. Several requests (none of
// which required authentication) were chosen to
// demonstrate how to use the REST API when
// authentication is not required.
cb.addItem({label:"Enter Name or ID, Make request",data:""});
cb.addItem({label:"Request Limit",data:"https://api.twitter.
com/1/account/rate_limit_status.json"});
cb.addItem({label:"Contributors",data:"https://api.twitter.com/1/users/
contributors.json?screen_name=screenName&skip_status=true"});
Twitter
cb.addItem({label:"Favorites",data:"https://api.twitter.com/1/favorites.
json?count=5&screen_name=screenName"});
cb.addItem({label:"Followers",data:"https://api.twitter.com/1/followers/
ids.json?cursor=-1&screen_name=screenName"});
cb.addItem({label:"Friends",data:"https://api.twitter.com/1/friends/ids.
json?cursor=-1&screen_name=screenName"});
cb.addItem({label:"Look Up",data:";https://api.twitter.com/1/users/lookup.
json?screen_name=screenName"});
cb.addItem({label:"Retweeted to user",data:"https://api.twitter.com/1/
statuses/retweeted_to_user.json?screen_name=screenName&count=3"});
cb.addItem({label:"Search",data:"http://search.twitter.com/search.json?
q=screenName&rpp=2&result_type=mixed&show_user=true"});
cb.addItem({label:"Show",data:"https://api.twitter.com/1/users/show.json?
screen_name=screenName"});
cb.addItem({label:"Suggestions",data:"https://api.twitter.com/1/users/
suggestions.json"});
cb.addItem({label:"Timeline",data:"http://api.twitter.com/1/statuses/user_
timeline.json?screen_name=screenName&count=5&include_rts=1"});
cb.addItem({label:"Trends",data:"https://api.twitter.com/1/trends/daily.
json"});
// Combobox listener
cb.addEventListener(Event.CHANGE,cbF);
}
// Combobox listener function
private function cbF(e:Event):void{
// The first combobox item is informational only.
// No request is sent.
if(cb.selectedItem.data!=""){
// If a request is sent, disable the combobox
// so requests cannot be repeatedly made until
// data is returned from previous request.
requestEnableF(false);
// Check if screen name is number (ID) or not.
if(isNaN(Number(screenName_tf.text))){
// If not a number, only need to replace
// screenName with the text from
// screenName_tf.
loadF(cb.selectedItem.data.replace("screenName",screenName_tf.text));
} else {
489
490
Chapter 11 n Social Gaming: Social Networks
// If it is a number, need to replace
// screenName with text from screenName_tf
// and replace screen_name with user_id
loadF(cb.selectedItem.data.replace("screenName",screenName_tf.text).replace
("screen_name","user_id"));
}
}
}
private function requestEnableF(b:Boolean):void{
// enable/disable cb
cb.enabled = b;
if(!b){
// If disabling, add message to return_tf
// informing user.
return_tf.appendText("Please wait. Request sent, return
pending...\n******* "+cb.selectedItem.label+":\n");
}
}
// Request return error handler
private function errorF(e:IOErrorEvent):void {
// Re-enable cb
requestEnableF(true);
return_tf.appendText(e+"\n"+e.text);
}
// Request complete handler
private function completeF(e:Event):void {
// Re-enable cb
requestEnableF(true);
// If you use Mike Chambers’ JSON class, uncomment
// the following line and comment out the line
// that follows.
//var obj:Object = JSON.decode(e.target.data);
// If using the Flash Native JSON class, leave
// this line uncommented.
var obj:Object=JSON.parse(e.target.data);
// obj is converted from JSON data to a Flash Object
evalF(obj);
// Scroll the TextArea to the most recent
// received data.
return_tf.verticalScrollPosition =
return_tf.maxVerticalScrollPosition;
}
// Recurrsive object evaluator. You almost certainly will
Twitter
// extract only a few bits of data returned in obj. You can
// use evalF to determine what bits you want to use and how
// to extract them without iterating through all of obj.
private function evalF(obj:*) {
if (obj is String | | obj is Number) {
return_tf.appendText(obj+"\n");
} else {
for (var s:* in obj) {
return_tf.appendText("| "+s+"\n");
evalF(obj[s]);
}
}
}
}
}
If you upload the PHP, HTML, and SWF files to your server, you should be able to
get a good idea of how to work with Twitter. With some requests (such as friends),
data will be returned that contains other Twitter user IDs. You can enter an ID in
screenName_tf and request more Twitter data.
By using data returned from Twitter, you can piggyback requests one after the other
for more sophisticated use of Twitter’s data.
Unfortunately, Twitter allows a limited number of requests per hour. If you make a
request on behalf of an authenticated user, the limit is per user. Without authentication, the rate limit is per IP address from which you are making the request.
The limit is 150 unauthenticated requests per hour and 350 authenticated requests
per hour, according to Twitter documents. Some basic tests showed that each
unauthenticated request is counted by Twitter as making somewhere between 2 and
10 requests. I have no idea how they’re counting, but it’s easy to exceed their
unauthenticated limit.
You can use Request Limit to see how many more requests you can make before
exceeding the limit, and you can see when your limit will be reset (in Greenwich
Mean Time). You can also see how many requests are charged against you after making another request by checking your limit before and after making a request.
Request Limit requests do not count against your limit.
491
492
Chapter 11 n Social Gaming: Social Networks
Requests That Require Authentication
You need to complete two main steps to use requests that require authentication.
First, you must register your game with Twitter. Second, you must either implement
the OAuth protocol or use a library like Tweetr that implements the protocol.
Game Registration
To use requests that require authentication (and those for which it is supported), you
must first register your game with Twitter at https://dev.twitter.com/apps/new. (See
Figure 11.12.)
Figure 11.12
The Twitter application registration page at https://dev.twitter.com/apps/new.
Source: Twitter® Inc.
Fill out their form, agree with all those rules, prove you are a human being by filling
out the Captcha form, and click the Create your Twitter Application button. You
should see what looks like Figure 11.13.
Twitter
Figure 11.13
After successful registration, copy your Consumer Key and Secret.
Source: Twitter® Inc.
Copy your Consumer Key and Consumer Secret. You will need both to make authenticated requests on behalf of users. Twitter uses these numbers to identify your game
when a user gives your game permission to make requests on his behalf.
Click the Settings tab (see Figure 11.14) and add an icon or change your settings if
needed. In particular, note the Application Type settings. If you need more than read
access, check the Read and Write or Read, Write, and Access Direct Messages
options.
493
494
Chapter 11 n Social Gaming: Social Networks
Figure 11.14
The Twitter application registration page after successful registration and clicking the Settings tab.
Source: Twitter® Inc.
Click Update This Twitter Application’s Settings if you made any changes or
additions.
Tweetr
Tweetr is a free open-source ActionScript library that implements the OAuth protocol and allows your game to make Twitter requests that require authentication. Setting up your game to use Tweetr is a multistep process.
Twitter
All the needed files are in /support files/Chapter 11/twitter/authentication. But to
ensure you have the latest files, go to http://wiki.swfjunkie.com/tweetr.
1. Download and extract swcs.zip.
2. Download and extract oauth_template.zip.
3. Download and extract docs.zip.
4. Download and extract proxy.zip.
5. Add tweetrWEB swc to your library path extracted from the swc.zip.
6. Go to http://code.google.com/p/as3crypto/downloads/list and download
as3crypto.swc.
7. Add as3crypto.swc to your library path. (See Figure 11.15a.)
Figure 11.15a
You should have both TweetrWEB.swc and as3crypto.swc in your library path, and both should have Link
Type: Merged into Code. Your paths will be different from that shown.
Source: Adobe Systems Incorporated.
495
496
Chapter 11 n Social Gaming: Social Networks
8. Find the js folder extracted in Step 2 and upload it to your server.
9. Find the proxy folder (that contains index.php, install.php, and Tweetr.php)
extracted in Step 4 and upload to your website.
10. Open your website’s install.php file in your browser and follow the steps to
create the needed files. Here is a walkthrough: http://wiki.swfjunkie.com/
tweetr:howtos:installing-the-proxy.
11. Create a verified.html page and upload it to your server.
12. Create twitter.html, your swf’s embedding HTML page, and upload it to your
server.
13. Upload the swfObject folder from /support files/Chapter 11/twitter/authentication.
verified.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script language="JavaScript" type="text/JavaScript"
src="js/tweetrOAuth.js"></script>
</head>
<body>
<script type="text/JavaScript">
OAuth.verify();
</script>
</body>
</html>
twitter.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<script language="JavaScript" type="text/JavaScript"
src="js/tweetrOAuth.js"></script>
<script language="JavaScript" type="text/JavaScript"
src="js/swfobject.js"></script>
</head>
<body>
<div id="flashDiv">
no flash
Twitter
</div>
<script type="text/JavaScript">
var so = new SWFObject("twitter.swf", "twitter", "700", "400", "9",
"#336699");
so.addParam("allowScriptAccess", "always");
so.write("flashDiv");
</script>
<script type="text/JavaScript">
OAuth.setFlashElement("twitter");
</script>
</body>
</html>
You’re now ready to create your game, twitter.swf. In the following document class
code, I’ll show you how to use the Tweetr class to obtain authentication and make
requests that require authentication.
(Warning: This code will fail because Twitter made changes that broke the code. Below
I show how to fix the problem.)
I think you should see how code that once worked and then was broken by Twitter
can be fixed because you are likely to encounter the same issue of needing to repair
code that once worked but was latter broken by changes made by Twitter. Twitter is
notorious for making frequent changes that break previously working code.
Main.as
package com.kglad{
import com.swfjunkie.tweetr.oauth.OAuth;
import com.swfjunkie.tweetr.oauth.events.OAuthEvent;
import com.swfjunkie.tweetr.Tweetr;
import com.swfjunkie.tweetr.data.objects.StatusData;
import com.swfjunkie.tweetr.data.DataParser;
import com.swfjunkie.tweetr.events.TweetEvent;
import com.swfjunkie.tweetr.utils.TweetUtil;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.filters.DropShadowFilter;
import flash.text.StyleSheet;
import flash.text.TextField;
import flash.net.SharedObject;
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
497
498
Chapter 11 n Social Gaming: Social Networks
import flash.utils.describeType;
/**
* Pinless OAuth Example AS3
* @author Sandro Ducceschi [swfjunkie.com, Switzerland]
*/
public class Main extends MovieClip {
public var oauth:OAuth;
private var tweetr:Tweetr;
private var so:SharedObject;
public function Main() {
// Login MovieClip is visible by default.
login_mc.buttonMode = true;
// Make the request combobox unavailable until
// authorization complete.
cb.visible = false;
clear_mc.visible = false;
clear_mc.buttonMode = true;
return_tf.text = "";
// Create or fetch a sharedobject to minimize
// Twitter logins.
so = SharedObject.getLocal("twitterSO");
// Initialize an OAuth instance
oauth = new OAuth();
// Assign the consumerKey and ConsumerSecret
// properties. You must change this to your key
// and secret.
oauth.consumerKey="la4QXat9lUweDKtf7Ew3g";
oauth.consumerSecret="3quoW0oEWCXlBZxROWCvaSQeAgxvIgZCaNceXlYxex0";
// Initialize a Tweetr instance, tweetr
tweetr = new Tweetr();
// Assign the serviceHost of tweetr to be your
// proxy directory.
// You must change this to your proxy.
tweetr.serviceHost="http://www.kglad.com/Files/twitter/proxy";
// Add listeners for request returns
tweetr.addEventListener(TweetEvent.STATUS, requestStatusF);
tweetr.addEventListener(TweetEvent.COMPLETE, requestCompleteF);
tweetr.addEventListener(TweetEvent.FAILED, requestErrorF);
// If a user has authorized your game in the past (and
// not deleted his SharedObject), use the stored oauthToken
// and oauthTokenSecret
if(so.data.oauthToken){
// Retrieve and assign username, oauthToken, and
Twitter
// oauthTokenSecret
oauth.oauthToken = so.data.oauthToken;
oauth.oauthTokenSecret = so.data.oauthTokenSecret;
oauth.username = so.data.username;
// Assign tweetr’s oAuth property.
this.tweetr.oAuth = oauth;
// Ready the request combobox
tweetrF();
// Allow use of clear_mc
clear_mc.visible = true;
clear_mc.addEventListener(MouseEvent.CLICK,clearReturnF);
} else {
// Assign callbackURL, needed for the Twitter
// authentication scheme when using pinless
// authorization.
oauth.callbackURL="http://www.kglad.com/Files/twitter/verified.html";
// You should use pinless authorization.
// Pin-based authorization should only be used
// for games not embedded in a web browser (like
// a game installed on a mobile device or desktop).
oauth.pinlessAuth=true;
// Add listeners
oauth.addEventListener(OAuthEvent.COMPLETE,
handleOAuthEvent);
oauth.addEventListener(OAuthEvent.ERROR, handleOAuthEvent);
// Login button listener
login_mc.addEventListener(MouseEvent.CLICK,clickF);
}
}
private function tweetrF():void{
// Make login button unavailable
login_mc.visible = false;
// Make request combobox available and populate it.
cb.visible = true;
cb.addItem({label:"Make Request",data:""});
cb.addItem({label:"Request Limit",data:"getRateLimitStatus"});
cb.addItem({label:"Favorites",data:"getFavorites"});
cb.addItem({label:"Followers",data:"getFollowers"});
cb.addItem({label:"Friends",data:"getFriends"});
cb.addItem({label:"Mentions",data:"getMentions"});
cb.addItem({label:"Retweets by You",data:"getRetweetsByMe"});
cb.addItem({label:"Retweets of You",data:"getRetweetsOfMe"});
cb.addItem({label:"Your Details",data:"getUserDetails"});
499
500
Chapter 11 n Social Gaming: Social Networks
cb.addItem({label:"Your Home Timeline",data:"getHomeTimeLine"});
// Combobox Event.CHANGE listener
cb.addEventListener(Event.CHANGE,cbF);
}
function cbF(e:Event):void{
// If anything other than "Make Request" is selected
if(cb.selectedItem.data!=""){
return_tf.appendText("Please wait. Request sent...\n");
return_tf.verticalScrollPosition =
return_tf.maxVerticalScrollPosition;
// Make combobox unavailable until request
// return complete
cb.visible = false;
if(cb.selectedItem.label=="Your Details"){
return_tf.appendText("Username|
"+oauth.username+"\n");
// Check the Tweetr API. This is for the
// getUserDetails method of the Tweetr class.
// Both here and in the else branch, Array
// notation is used to convert the
// cb.selectedItem.data string
// to a Tweetr method
tweetr[cb.selectedItem.data](oauth.username);
} else {
// Array notation is used to convert the
// cb.selectedItem.data string to a Tweetr
// method
tweetr[cb.selectedItem.data]();
}
}
}
function clickF(e:MouseEvent):void{
return_tf.appendText("Creating OAuth Authorization Window. This
will launch a popup-window.\nIf your browser blocks it, allow popups and reload the
page.\n");
// Request authorization token.
oauth.getAuthorizationRequest();
}
// return from oauth.getAuthorizationRequest();
private function handleOAuthEvent(e:OAuthEvent):void {
if (e.type==OAuthEvent.COMPLETE) {
// Allow use of clear_mc
clear_mc.visible = true;
Twitter
clear_mc.addEventListener(MouseEvent.CLICK,
clearReturnF);
return_tf.appendText("Authorization
Successful!\n"+oauth.toString()+"\n");
// Assign oAuth property of tweetr
this.tweetr.oAuth = oauth;
// Save oauthToken, oauthTokenSecret, and
// username to SharedObject
so.data.oauthToken = oauth.oauthToken;
so.data.oauthTokenSecret = oauth.oauthTokenSecret;
so.data.username = oauth.username;
// Ready the request combobox
tweetrF();
} else {
return_tf.appendText(e.text+"\n");
}
}
private function clearReturnF(e:MouseEvent):void{
return_tf.text = "";
}
private function requestCompleteF(e:TweetEvent):void {
return_tf.appendText("Complete ******\n"+e.info+"\n");
responseF(e);
}
private function requestStatusF(e:TweetEvent):void{
// This can be used for debugging.
//return_tf.appendText("Status ******\n"+e.info+"\n");
//responseF(e);
}
private function requestErrorF(e:TweetEvent):void {
return_tf.appendText("Error ******\n"+e.info+"\n");
responseF(e);
}
private function responseF(e:TweetEvent):void{
cb.visible = true;
// Tweetr uses XML return data instead of JSON for
// everything except trends, which is only supplied
// by Twitter in JSON format. For non-trend data, I
// am displaying the entire xml returned by Twitter.
// You will probably want to use a small part
// of what is returned: parse the return using the
// methods of the Flash XML class.
if(tweetr.returnType.indexOf("trends")>-1){
evalF(JSON.parse(e.data.toString()));
501
502
Chapter 11 n Social Gaming: Social Networks
} else {
return_tf.appendText(e.data+"******\n");
}
return_tf.verticalScrollPosition =
return_tf.maxVerticalScrollPosition;
}
private function evalF(obj:*) {
if (obj is String || obj is Number) {
return_tf.appendText(obj+"\n");
} else {
for (var s:* in obj) {
return_tf.appendText("| "+s+"\n");
evalF(obj[s]);
}
}
}
}
}
There is a problem. None of the trend methods (currentTrends,
weeklyTrends) works.
dailyTrends, trends,
Even worse, after writing this code and verifying that everything (except the trend
methods) was working, Twitter must’ve made some changes, because nothing was
working when I reviewed this chapter. I was seeing security sandbox errors for all
the requests (except the trend methods, which trigger 404 Page Not Found errors).
Because SWC files contain compiled code, you cannot check or edit them (unless you
decompile them). Fortunately, Tweetr is open source, and the author has made the
source code available.
So, if you remove the SWC from the library path and add the source files, you can
use the source code. That way, you can edit the source code and correct the
problems.
Click File > Publish Settings > ActionScript 3.0 Settings > Library Path and click the
Tweetr SWC to select it. Then click the Remove Selected Path icon (the minus sign)
to remove the SWC. (See Figure 11.15b.)
Twitter
Remove
selected
path icon
Figure 11.15b
Library Path panel with tweetrWEB.swc selected.
Source: Adobe Systems Incorporated.
Navigate to http://wiki.swfjunkie.com/tweetr:downloads and download the Tweetr
source code package. Extract the files and copy the com directory to the directory
that contains your game FLA.
The first problem occurs when the user tries to log in and oauth.getAuthorizationRequest()
executes. There is a security sandbox error thrown because the SWF in your domain
is trying to load data from twitter.com.
To fix that error, you need to edit the OAuth class. So, open OAuth.as in the
com.swfjunkie.tweetr.oauth directory. When you do that, you will see these two
CONFIG constants:
503
504
Chapter 11 n Social Gaming: Social Networks
CONFIG::AIR
CONFIG::WEB
These are conditional compilation constants. By defining one of these constants true
and the other false, you can use the same class files to publish two different versions
of OAuth.
To define the compilation constants, click File > Publish Settings > ActionScript 3.0
Settings > Config Constants (see Figure 11.16).
Figure 11.16
The Config Constants panel, where you define conditional compilation constants.
Source: Adobe Systems Incorporated.
Click the Add Config Constant icon (plus sign) and change CONFIG::CONFIG_
CONST to CONFIG::AIR. Then double-click the Value column next to CONFIG::AIR
Twitter
and type false. Likewise, add CONFIG::WEB and assign a value of true. Obviously, if
you want to publish an AIR version, assign opposite values to those two constants.
Now check the getAuthorizationRequest function. You can see urlLoader is trying to
load data from twitter.com (OAUTH_DOMAIN = "http://twitter.com").
Let’s route that communication through gateway.php, just like we did with the
requests that do not need authentication. In the variables section, declare urlVar and
urlReq.
private var urlVar:URLVariables;
private var urlReq:URLRequest = new URLRequest("gateway.php");
Then change
getAuthorizationRequest
to:
public function requestAccessToken(verifier:String):void {
request=ACCESS;
this.verifier=verifier;
var urlRequest:URLRequest = new URLRequest(OAUTH_DOMAIN+ACCESS);
urlRequest.url=_serviceHost+ACCESS+"?"+getSignedRequest("GET",urlRequest.url);
urlVar.requestURL=urlRequest.url;
urlReq.data=urlVar;
urlLoader.load(urlReq);
}
If you test now, you will be able to start the authentication process, but you will be
unable to finish because of another security sandbox violation. We need to edit
requestAccessToken:
public function requestAccessToken(verifier:String):void {
request=ACCESS;
this.verifier=verifier;
var urlRequest:URLRequest = new URLRequest(OAUTH_DOMAIN+ACCESS);
urlRequest.url=_serviceHost+ACCESS+"?"+getSignedRequest("GET",urlRequest.
url);
urlVar.requestURL=urlRequest.url;
urlReq.data=urlVar;
urlLoader.load(urlReq);
}
After those two functions are fixed, you should be able to complete the authentication process. And all the non-trend requests work because the SWF is accessing
twitter.com via the (local) proxy files.
As long as we are fixing the Tweetr files, we might as well fix the trend methods in
the Tweetr class. Open Tweetr.as in com.swfjunkie.tweetr and find the trend
constants:
505
506
Chapter 11 n Social Gaming: Social Networks
private static const URL_TWITTER_TRENDS:String =
"http://search.twitter.com/trends.json";
private static const URL_TWITTER_TRENDS_CURRENT:String =
"http://search.twitter.com/trends/current.json";
private static const URL_TWITTER_TRENDS_DAILY:String =
"http://search.twitter.com/trends/daily.json";
private static const URL_TWITTER_TRENDS_WEEKLY:String =
"http://search.twitter.com/trends/weekly.json";
Replace them with the updated URLs from the REST API and comment out the
currentTrends method (which no longer exists).
private static const URL_TWITTER_TRENDS:String = "https://api.twitter.com/1/trends/
1.json";
private static const URL_TWITTER_TRENDS_DAILY:String = "https://api.twitter.com/1/
trends/daily.json";
private static const URL_TWITTER_TRENDS_WEEKLY:String = "https://api.twitter.com/1/
trends/weekly.json";
Add trend items to the
cb
combobox in Main.as:
private function tweetrF():void {
// Make login button unavailable
login_mc.visible=false;
// Make request ComboBox visible and populate it.
cb.visible=true;
cb.addItem({label:"Make Request",data:""});
cb.addItem({label:"Request Limit",data:"getRateLimitStatus"});
cb.addItem({label:"Favorites",data:"getFavorites"});
cb.addItem({label:"Followers",data:"getFollowers"});
cb.addItem({label:"Friends",data:"getFriends"});
cb.addItem({label:"Mentions",data:"getMentions"});
cb.addItem({label:"Retweets by You",data:"getRetweetsByMe"});
cb.addItem({label:"Retweets of You",data:"getRetweetsOfMe"});
cb.addItem({label:"Current Trends",data:"trends"});
cb.addItem({label:“Daily Trends”,data:“dailyTrends”});
cb.addItem({label:“Weekly Trends”,data:“weeklyTrends”});
cb.addItem({label:“Your Details”,data:“getUserDetails”});
cb.addItem({label:“Your Home Timeline”,data:“getHomeTimeLine”});
// ComboBox Event.CHANGE listener
cb.addEventListener(Event.CHANGE,cbF);
}
If you test now, you will trigger security sandbox errors for the trend methods. We’ll
fix those exactly like we did before using gateway.php.
Twitter
Replace the trend methods in the Tweetr class with:
public function trends():void {
_returnType=RETURN_TYPE_TRENDS_RESULTS;
setGETRequest();
urlVar.requestURL=URL_TWITTER_TRENDS;
urlReq.data=urlVar;
urlLoader.load(urlReq);
//urlLoader.load(new URLRequest(URL_TWITTER_TRENDS));
}
/**
* Returns the current top 10 trending topics on Twitter.
*/
/*
public function currentTrends():void{
_returnType = RETURN_TYPE_TRENDS_RESULTS;
setGETRequest();
urlLoader.load(new URLRequest(URL_TWITTER_TRENDS_CURRENT));
}
*/
/**
* Returns the top 20 trending topics for each hour in a given day.
*/
public function dailyTrends():void {
_returnType=RETURN_TYPE_TRENDS_RESULTS;
setGETRequest();
urlVar.requestURL=URL_TWITTER_TRENDS_DAILY;
urlReq.data=urlVar;
urlLoader.load(urlReq);
// urlLoader.load(new URLRequest(URL_TWITTER_TRENDS_DAILY));
}
/**
* Returns the top 30 trending topics for each day in a given week.
*/
public function weeklyTrends():void {
_returnType=RETURN_TYPE_TRENDS_RESULTS;
setGETRequest();
urlVar.requestURL=URL_TWITTER_TRENDS_WEEKLY;
urlReq.data=urlVar;
urlLoader.load(urlReq);
// urlLoader.load(new URLRequest(URL_TWITTER_TRENDS_WEEKLY));
}
Now everything should work.
507
508
Chapter 11 n Social Gaming: Social Networks
There is a DataParser class that is part of Tweetr, but you will need to do error checking before passing any data to it, because it doesn’t appear that there is any error
checking in DataParser. So, for example, if your game triggers a request-limit error
message, you will need to check for that before using any of the DataParser methods
on the returned data.
Google+
Just like Facebook and Twitter, the simplest use of Google+ with a game is to add one
(or more) of Google+’s buttons, such as the +1, Badge, or Share button. Adding a button requires you to add some HTML tags to the SWF’s embedding HTML file and
include the https://apis.google.com/js/plusone.js file. In addition, you can customize
what Google+ members see when they share your content by adding meta tags.
If you go to https://developers.google.com/+/plugins, you can see samples of the
Google+ buttons and snippet display. See Figure 11.17 for a partial screenshot of
that page that shows the various Google+ buttons and a snippet.
Figure 11.17
The Google+ +1 button, Share button, Badge button, and snippet display.
Source: Google® Inc.
Google+
Adding a +1 Button
To add a +1 button, add one (or more) of the following divs after the opening body
tag in your HTML, followed by the JavaScript needed to include https://apis.google.
com/js/plusone.js. Adding the data-annotation attribute allows for different +1 button
appearances.
<div class="g-plusone" data-annotation="none"></div>
<!-- inline annotation. a width of 120 is minimal. if you increase the width some
additional text ("Recommend this on Google") will appear after the number of
recommendations. -->
<div class="g-plusone" data-annotation="inline" data-width="120"></div>
<!-- bubble annotation -->
<div class="g-plusone"></div>
<!-- asynchronous loading of https://apis.google.com/js/plusone.js so it does not
delay execution of code following the +1 button code -->
<script type="text/javascript">
(function() {
var po = document.createElement(’script’);
po.type = ’text/javascript’;
po.async = true;
po.src = ‘https://apis.google.com/js/plusone.js’;
var s = document.getElementsByTagName(’script’)[0];
s.parentNode.insertBefore(po, s);
})();
</script>
Adding a Badge
The Google+ Badge links directly to your profile. You need to use your Google+ ID
in the data-href attribute, and you need to include https://apis.google.com/js/plusone.
js. To find your Google+ ID, go to your Google+ page and click Profile. Your ID is
the number in the URL between “google.com/” and “/posts.”
The following div will add a Google+ Badge linking to your profile if you replace
your_google_plus_id with your Google+ ID.
<div class="g-plus" data-height="69"
data-href="//plus.google.com/your_google_plus_id?rel=author"></div>
<script type="text/javascript">
(function() {
var po = document.createElement(’script’);
po.type = ’text/javascript’;
po.async = true;
509
510
Chapter 11 n Social Gaming: Social Networks
po.src = ‘https://apis.google.com/js/plusone.js’;
var s = document.getElementsByTagName(’script’)[0];
s.parentNode.insertBefore(po, s);
})();
</script>
Adding a Share Button
The Google+ Share button allows Google+ members to share a link to your site with
their friends. Again, you need to include https://apis.google.com/js/plusone.js.
You can customize what members see in the shared link by using Goggle+ snippets.
The following div will add a Google+ Share button.
<div class="g-plus" data-action="share"></div>
<script type="text/javascript">
(function() {
var po = document.createElement(’script’);
po.type = ’text/javascript’;
po.async = true;
po.src = ‘https://apis.google.com/js/plusone.js’;
var s = document.getElementsByTagName(’script’)[0];
s.parentNode.insertBefore(po, s);
})();
</script>
To customize what Google+ members see in your shared link, you can use meta tags
within your HTML file’s head tags and add itemscope and itemtype attributes to your
HTML tag. For example, the following customizes the snippet’s title and description
and adds an image.
<meta itemprop="name" content="KGLAD TEST">
<meta itemprop="description" content="kglad test description">
<meta itemprop="image" content="kg.png">
Google+ Plug-In Summary
Here is the code for all the Google+ buttons and snippet customization in the context
of a SWF’s embedding HTML. There are two files with this code at /support files/
Chapter 11/googlePlus/googlePlus_01/index.html and /support files/Chapter 11/
googlePlus/googlePlus_00/oauth2callback/index.html.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/
xhtml1/DTD/xhtml1-strict.dtd">
<!-- Add itemscope and itemtype attributes to your html tag and use meta tags with
itemprop attributes to assign snippets (a preview that is seen when a post contains a
link to your game). -->
Google+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" itemscope itemtype=
"http://schema.org/Product">
<head>
<!-- These attributes are used in google plus snippets. -->
<meta itemprop="name" content="KGLAD TEST">
<meta itemprop="description" content="kglad test description">
<meta itemprop="image" content="kg.png">
<title>index</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css" media="screen">
html, body { height:100%; background-color: #ffffff;}
body { margin:0; padding:0; overflow:hidden; }
#flashContent { width:100%; height:100%; }
</style>
</head>
<body>
<!--google plus +1 button with various annotations. You need to include the
https://apis.google.com/js/plusone.js file for these buttons to appear and function -->
<div class="g-plusone" data-annotation="none"></div> <br/>
<!-- inline annotation. a width of 120 is minimal. if you increase the width
some additional text ("Recommend this on Google") will appear after the number of
recommendations. -->
<div class="g-plusone" data-annotation="inline" data-width="120"></div> </br>
<!-- bubble annotation -->
<div class="g-plusone"></div> <br/>
<!-- google plus badges (link directly to your profile). You need to include the
https://apis.google.com/js/plusone.js file for these buttons to appear and function -->
<!-- To find your google plus id, go to your google plus page and click profile.
Your id is the number in the url between google.com/ and /posts -->
<div class="g-plus" data-height="69" datahref="//plus.google.com/105564099866221383831?rel=author"></div> <br/>
<!-- google plus share button. If you assign a snippet, it will be seen when a user
shares your link. -->
<div class="g-plus" dataaction="share"></div><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/>–>
<!-- asynchronous loading of https://apis.google.com/js/plusone.js so it does not
delay execution of code following the +1 button code -->
<script type="text/javascript">
(function() {
var po = document.createElement(’script’);
511
512
Chapter 11 n Social Gaming: Social Networks
po.type = ’text/javascript’;
po.async = true;
po.src = ‘https://apis.google.com/js/plusone.js’;
var s = document.getElementsByTagName(’script’)[0];
s.parentNode.insertBefore(po, s);
})();
</script>
<div id="flashContent">
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="800"
height="400" id="index" align="middle">
<param name="movie" value="index.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="play" value="true" />
<param name="loop" value="true" />
<param name="wmode" value="window" />
<param name="scale" value="showall" />
<param name="menu" value="true" />
<param name="devicefont" value="false" />
<param name="salign value=" />
<param name="allowScriptAccess" value="sameDomain "/>
<!--[if !IE]>-->
<object type="application/x-shockwave-flash" data="index.swf"
width="800" height="400">
<param name="movie" value="index.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="play" value="true" />
<param name="loop" value="true" />
<param name="wmode" value="window" />
<param name="scale" value="showall" />
<param name="menu" value="true" />
<param name="devicefont "value="false" />
<param name="salign" value="" />
<param name="allowScriptAccess" value="sameDomain" />
<!--<![endif]-->
<a href="http://www.adobe.com/go/getflash">
<img src="http://www.adobe.com/images/shared/download_buttons/
get_flash_player.gif" alt="Get Adobe Flash player" />
</a>
<!--[if !IE]>-->
</object>
<!--<![endif]-->
</object>
Google+
</div>
</body>
</html>
Google+ API
The Google+ API is explained at https://developers.google.com/+/api. It has many
similarities to the Facebook and Twitter APIs.
Google+’s API is also a REST API, and Google+ (like Facebook and Twitter) also has,
among others, a JavaScript library. (See Figure 11.18.) Unlike Twitter and Facebook,
there is no Google+ ActionScript library at the time of this writing.
Figure 11.18
Screenshot of the Google+ libraries at https://developers.google.com/+/downloads.
Source: Google® Inc.
First, I will show you how to use the Google+ REST API, starting with two different
SWFs—one that requests an authorization token and the second that uses the
authorization token to query Google+. Those files are in /support files/Chapter 11/
googlePlus/googlePlus_00.
Second, I will show you how to combine the code into one SWF. Those files are in
/support files/Chapter 11/googlePlus/googlePlus_01.
Third, we will separate the Google+ code from the user interface code creating the
Google+ library. Those files are in /support files/Chapter 11/googlePlus/googlePlus_02.
Finally, we will create a Google+ SWC file from the Google+ library. The SWC and
the files that use it are in /support files/Chapter 11/googlePlus/googlePlus_03.
To start, you must register your game with Google. Navigate to https://code.google.
com/apis/console/?pli=1# and click Create Project. (See Figure 11.19.)
513
514
Chapter 11 n Social Gaming: Social Networks
Figure 11.19
Google Create Project page.
Source: Google® Inc.
Then select the Google+ service you want to use (the Google+ API as shown in Figure 11.20) by toggling the off button to on (Figure 11.21).
Toggle google+ on
Figure 11.20
Select the Google+ service.
Source: Google® Inc.
Google+
Figure 11.21
Google+ toggled on.
Source: Google® Inc.
Click the API Project combobox (see Figure 11.22) and click Rename. Enter the
name of your game and click Save. (I entered Test Game, so that’s what you’ll see
after Figure 11.22.)
If you have more than one project/game, you will see them listed in the combobox
under Recent Projects.
API project
combobox
Figure 11.22
Click the API Project combobox to rename (or delete) your game.
Source: Google® Inc.
Click the API Access tab and click Create an OAuth 2.0 Client ID. (See Figure 11.23.)
515
516
Chapter 11 n Social Gaming: Social Networks
Create an
OAuth 2.0
client ID button
Figure 11.23
Click the API Access tab and click Create an OAuth 2.0 Client ID.
Source: Google® Inc.
Enter a product (game) name and, if desired, a logo. Click Next, check Web Application, and enter your host site URL after selecting http or https protocol. Finally, click
Create Client ID. (See Figure 11.24.)
Create client
ID button
Figure 11.24
After checking Web Application, enter your host site URL, select the appropriate protocol, and click Create
Client ID.
Source: Google® Inc.
Google+
After a brief delay, you should be back at your API Access page, and it should look
something like Figure 11.25.
API key
Client ID
Figure 11.25
API Access page after creating an OAuth ID.
Source: Google® Inc.
You need your client ID to retrieve an authorization token, and you use that authorization token to make Google+ requests that require authorization. You need your
API key to make Google+ requests that do not require authorization.
Also, note your Redirect URIs from the API Access page. If you retrieve an authorization token, that Redirect URI is called by the Google+ server. The authorization
token is appended to the URI as a query string.
You can retrieve the query string using the JavaScript href property of
window.location in the embedding HTML. Because you need only a few lines
of JavaScript to return that href property, this is a convenient place to mention
JavaScript injection.
JavaScript injection has been used since Flash Player 8, when the ExternalInterface
class was added to the ActionScript 2.0 API. The best explanation I have seen is
from Peter McBride at www.actionscript.org/resources/articles/745/1/JavaScriptand-VBScript-Injection-in-ActionScript-3/Page1.html, where both JavaScript and
VisualBasic (for Internet Explorer) injection are discussed.
517
518
Chapter 11 n Social Gaming: Social Networks
For the script needed to retrieve an HTML page’s address:
<script>
function(){
return window.location.href;
}
</script>
we can inject it into the embedding HTML (and nicely format it) using the following
ActionScript.
var href_js :XML =
<script>
<![CDATA[
function(){
return window.location.href;
}
]]>
</script>
var urlS:String = ExternalInterface.call(href_js);
There are two sample Flash applications that use the Google+ API. The first is in
/support files/Chapter 11/googlePlus/googlePlus_00 and uses two different SWF and
HTML files. One pair (googlePlus.html and googlePlus.swf) requests authorization,
and the other (in /support files/Chapter 11/googlePlus/googlePlus_00/oauth2callback),
index.html and index.swf, is the callback URI.
The second sample is in /support files/Chapter 11/googlePlus/googlePlus_01,
index.html and index.swf. Both the code for the authorization request and the code
to handle Google+ API requests are in index.swf.
Sample 1 Code: Separate Authorization File and Request File
com.kglad.Main—document class for authorization request, googlePlus.swf
package com.kglad {
import flash.display.MovieClip;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.net.URLRequestMethod;
import flash.net.navigateToURL;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLVariables;
public class Main extends MovieClip {
// The http request string (assmebled in the constructor).
Google+
private var OAuthREQ:String
// The http requests and responses are routed through
// gateway.php to avoid cross-domain issues.
private var urlR:URLRequest = new URLRequest(“gateway.php”);
private var urlLoader:URLLoader = new URLLoader();
private var urlVar:URLVariables = new URLVariables;
public function Main() {
// Assemble the https request. The url has scope,
// state, redirect uri, response type, and client id
// appended as a query string
OAuthREQ = “https://accounts.google.com/o/oauth2/auth?”;
// To access user data, you need to specify the scope
// of access. At the time of this writing, there is only
// one scope for Google+.
OAuthREQ += “scope=https://www.googleapis.com/auth/plus.me&”;
OAuthREQ += “state=/profile&”;
// This part of the query string must match the
// Google+ console API Access Redirect URIs.
OAuthREQ +=
“redirect_uri=http://www.kglad.com/Files/gp/oauth2callback&”;
OAuthREQ += “response_type=token&”;
// This part of the query string must match the
// Google+ console API Access Client ID.
OAuthREQ += “client_id=205719172028.apps.googleusercontent.com”;
init();
}
private function init():void{
// Add listener
urlLoader.addEventListener(Event.COMPLETE,loadCompleteF);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR,loadErrorF);
// Assign the requestURL of urlVar to OAuthREQ
urlVar.requestURL = OAuthREQ;
// POST the data to gateway.php, where requestURL is
// parsed and used to call
// https://accounts.google.com/o/oauth2/auth
urlR.method = URLRequestMethod.POST;
// Assign data to urlR
urlR.data = urlVar;
// Call gateway.php posting the https request.
urlLoader.load(urlR);
}
private function loadCompleteF(e:Event):void{
// Should return a response from Google+ that
// contains the redirect uri and access token.
519
520
Chapter 11 n Social Gaming: Social Networks
// The response is html
if(urlLoader.data.indexOf(’A HREF=“’)>-1){
// Extract the uri.
var htmlS:String = urlLoader.data.split(’A
HREF=“’)[1].split(’“’)[0];
// Convert html & to &
htmlS = htmlS.split(“&”).join(“&”);
// Open the redirect uri with query string
// containing the access token and expiration
// (of the access token) time.
navigateToURL(new URLRequest(htmlS),"_self");
} else {
trace(urlLoader.data);
}
}
private function loadErrorF(e:Event):void{
trace(e.toString());
}
}
}
com.kglad.Main—document class for the callback file, index.swf
package com.kglad {
import flash.display.MovieClip;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.net.URLRequestMethod;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLVariables;
import flash.external.ExternalInterface
import fl.controls.ComboBox;
import flash.events.MouseEvent;
public class Main extends MovieClip {
// API key from Google console/API Access
private const API_KEY:String = “AIzaSyBX9D4F6xmS773t31NEphlJJlJok-IZjcQ”;
// Define variables
private var urlR:URLRequest = new URLRequest(“gateway.php”);
private var urlLoader:URLLoader = new URLLoader();
private var urlVar:URLVariables = new URLVariables;
private var access_token:String;
private var help_uri_obj:Object;
private var resourceS:String;
private var methodS:String;
Google+
public function Main() {
stage.focus = input_tf;
// Inject JavaScript into the swf’s embedding html file.
// Used to return the embedding html’s url. The url
// contains google’s access token as a query string.
var href_js :XML =
<script>
<![CDATA[
function(){
return window.location.href;
}
]]>
</script>
var urlS:String = ExternalInterface.call(href_js);
// If not testing in Flash test environment
if(urlS){
// If this is the Google+ callback with
// access_toekn
if(urlS.split(“access_token=”)[1]){
access_token =
urlS.split(“access_token=”)[1].split(“&”)[0];
urlLoader.addEventListener(Event.COMPLETE,loadCompleteF);
// Initialize User Interface
initUI();
} else {
trace(“Error”);
}
}
urlR.method = URLRequestMethod.POST;
urlLoader.addEventListener(IOErrorEvent.IO_ERROR,loadErrorF);
}
private function initUI():void{
// Initialize the comboboxes
init_cbF();
// Initialize a help_uri object used to display help
// and assign the uri when one of the comboboxes
// changes
init_help_uri_objF();
// Initialize the call API button
init_callF();
// Initialize the clear button
init_clearF();
clearF();
}
521
522
Chapter 11 n Social Gaming: Social Networks
// the call API listener function
private function APIcallF(uri:String):void{
// All the data sent to google plus is in uri
urlVar.requestURL = uri;
urlR.data = urlVar;
urlLoader.load(urlR);
}
private function loadCompleteF(e:Event):void{
// Data received after calling the google plus API is
// displayed in the received_ta TextArea
receivedF(urlLoader.data);
}
private function loadErrorF(e:Event):void{
receivedF(e.toString());
}
private function receivedF(s:String):void{
received_ta.appendText("******************** Begin Data
********************\n"+"****** ";+resourceS+"/"+methodS+": Input Param = "+input_tf.
text+" ******\n"+s+"\n******************** End Data ********************\n");
received_ta.verticalScrollPosition =
received_ta.maxVerticalScrollPosition;
}
private function init_cbF():void{
// Labels and change listeners added to the three comboboxes
with(people_cb){
addItem({label:“Select an API Call”});
addItem({label:“get”});
addItem({label:“search”});
addItem({label:“listByActivity”});
addEventListener(Event.CHANGE,cbChangeF);
}
with(activities_cb){
addItem({label:“Select an API Call”});
addItem({label:“get”});
addItem({label:“list”});
addItem({label:“search”});
addEventListener(Event.CHANGE,cbChangeF);
}
with(comments_cb){
addItem({label:“Select an API Call”});
addItem({label:“get”});
addItem({label:“list”});
addEventListener(Event.CHANGE,cbChangeF);
Google+
}
}
private function cbChangeF(e:Event):void{
// Clear the Input Parameters textfield
input_tf.text = “”;
// The resource string is derived from the combobox name
resourceS = e.currentTarget.name.split(“_”)[0];
// The method is the label as long as Select an API Call
// is not selected
methodS = e.currentTarget.selectedLabel;
if(methodS.indexOf(“Select”)==-1){
// helpF displays help for calling a
// particular API
helpF(resourceS,methodS);
}
}
// The help_uri_obj is defined. It is an Object that uses
// the resource (people, activites, comments) and the method
// (get, search, list, listByActivity) to store help text and
// function name.
private function init_help_uri_objF():void{
help_uri_obj = {};
help_uri_obj[“people”] = {};
// The second parameter is a function that returns the
// uri string. This allows the most recent input_tf.text
// to be used in the API calls.
help_uri_obj[“people”][“get”] = ["Get a person’s profile. me
(the game player) is used by default. If you want a different profile, enter their user
ID (not name) in the Input Parameter textfield. You can use People/search to find ID’s
using names”,peopleGetF];
help_uri_obj[“people”][“search”] = [“Search all public profiles.
Enter your search term (person’s name) in the Input Parameter textfield.”,peopleSearchF];
help_uri_obj[“people”][“listByActivity”] = [“List all of the people
in the specified collection for a particular activity (post) where collection = \”resharers\"
or \“plusoners\”. In this example plusoners is used. Enter activity ID in the Input Parameter
textfield. Find activity ID’s by using Activities/list.",peopleListByActivityF];
help_uri_obj[“activities”] = {};
help_uri_obj[“activities”][“get”] = [“Get an activity (post) by
id. Enter the activity ID in the Input Parameter textfield. An activity ID can be found
using Activities/list.”,activitiesGetF];
help_uri_obj[“activities”][“list”] = [“List all of the activities (posts) for a particular user. Enter the user ID in the Input Parameter textfield
or the default \”me\" (the game player) will be used.",activitiesListF];
523
524
Chapter 11 n Social Gaming: Social Networks
help_uri_obj[“activities”][“search”] = [“Search public activities
(posts). Enter a search string in the Input Parameter textfield.”,activitiesSearchF];
help_uri_obj[“comments”] = {};
help_uri_obj[“comments”][“get”] = [“Get a comment by ID. Find a
comment ID from Comments/list and enter it in the Input Parameter textfield.”,commentsGetF];
help_uri_obj[“comments”][“list”] = [“List all of the comments
for an activity (post) by ID. Enter the activity ID in the Input Parameter textfield. An
activity ID can be found using Activities/list.”,commentsListF];
}
private function helpF(resourceS,methodS):void{
// The first array element of help_uri_obj is the
// help text displayed when a combobox changes.
help_tf.text = help_uri_obj[resourceS][methodS][0];
}
private function init_callF():void{
// The call API button listener
call_btn.addEventListener(MouseEvent.CLICK,callF);
}
private function callF(e:MouseEvent):void{
// The second array element of help_uri_obj is the
// function to be called and returns the
// google plus uri.
var uri:String = help_uri_obj[resourceS][methodS][1]();
//trace(uri);
APIcallF(uri);
}
private function init_clearF():void{
// The clear Received Data TextArea listener
clear_btn.addEventListener(MouseEvent.CLICK,clearF);
}
private function clearF(e:MouseEvent=null):void{
received_ta.text = “”;
}
// The functions called in callF that return the google
// plus uris. The escape function url-encodes strings
// converting, for example, spaces into the url-safe %20
private function peopleGetF():String{
if(input_tf.text.length<1 || input_tf.text=="me"){
return
"https://www.googleapis.com/plus/v1/people/me?access_token="+access_token;
} else {
return
"https://www.googleapis.com/plus/v1/people/"+escape(input_tf.text)+"?key="+API_KEY;
}
Google+
}
private function peopleSearchF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/people?query=glad&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/people?query="+escape(input_tf.text)+"
&maxResults=20"+"&key="+API_KEY;
}
}
private function peopleListByActivityF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities/z12ow5iraufyz5hgw04cc1nzenjrjvlinhg/
people/plusoners?key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities/"+escape(input_tf.text)+"/people/
plusoners?key="+API_KEY;
}
}
private function activitiesGetF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities/z12ow5iraufyz5hgw04cc1nzenjrjvlinhg?
alt=json&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities/"+escape(input_tf.text)+"?alt=
json&key="+API_KEY;
}
}
private function activitiesListF():String{
if(input_tf.text.length<1 || input_tf.text=="me"){
return
"https://www.googleapis.com/plus/v1/people/me/activities/public?access_token=
"+access_token;
} else {
return
"https://www.googleapis.com/plus/v1/people/"+escape(input_tf.text)+"/activities/
public?alt=json&key="+API_KEY;
}
}
private function activitiesSearchF():String{
525
526
Chapter 11 n Social Gaming: Social Networks
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities?query=help&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities?query="+escape(input_tf.text)+
"&key="+API_KEY;
}
}
private function commentsGetF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/comments/zUgUkZP-t0npF0jntaPA3G-VBTt6blguPAmJjNBI_LSktqQLLZyqTA5SViPGXU6rJ3Tzu706Zk?key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/comments/"+escape(input_tf.text)+"?key=
"+API_KEY;
}
}
private function commentsListF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities/z12ow5iraufyz5hgw04cc1nzenjrjvlinhg/
comments?alt=json&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities/"+escape(input_tf.text)+"/comments?
alt=json&key="+API_KEY;
}
}
}
}
Sample 2 Code: Authorization and Requests in One File
com.kglad.Main—document class for file with authorization and requests code
package com.kglad {
import flash.display.MovieClip;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.net.URLRequestMethod;
import flash.net.navigateToURL;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLVariables;
Google+
import
import
import
public
flash.external.ExternalInterface
fl.controls.ComboBox;
flash.events.MouseEvent;
class Main extends MovieClip {
// API key from Google console/API Access
private const API_KEY:String = "AIzaSyA6BBrmHCsL5trpQzj4oIXUBxAkCZwHcWU";
private var OAuthREQ:String
// Define variables
private var urlR:URLRequest = new URLRequest("gateway.php");
private var urlLoader:URLLoader = new URLLoader();
private var urlVar:URLVariables = new URLVariables;
private var access_token:String;
private var help_uri_obj:Object;
private var resourceS:String;
private var methodS:String;
public function Main() {
stage.focus = input_tf;
urlR.method = URLRequestMethod.POST;
// Inject JavaScript into the swf’s embedding html file.
// Used to return the embedding html’s url. The url
// contains google’s access token as a query string.
var href_js :XML =
<script>
<![CDATA[
function(){
return window.location.href;
}
]]>
</script>
var urlS:String = ExternalInterface.call(href_js);
// If not testing in Flash test environment
if(urlS){
// This if-else statement is the code that allows
// both the initial request for an access token
// and the processing of the access token to be
// in one file. If this is the Google+ callback
// with access_token
if(urlS.split(“access_token=”)[1]){
access_token =
urlS.split(“access_token=”)[1].split(“&”)[0];
urlLoader.addEventListener(Event.COMPLETE,loadCompleteF);
// Initialize User Interface
initUI();
} else {
// If this is initial load of page,
// request access_token.
527
528
Chapter 11 n Social Gaming: Social Networks
OAuthREQ =
“https://accounts.google.com/o/oauth2/auth?”;
//OAuthREQ +=
“scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/
auth/urlshortener+https://www.googleapis.com/auth/plus.me+https://www.googleapis.
com/auth/userinfo.profile&”;
//OAuthREQ +=
“scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/
auth/userinfo.profile&”;
OAuthREQ +=
“scope=https://www.googleapis.com/auth/plus.me&”;
OAuthREQ += “state=/profile&”;
OAuthREQ +=
“redirect_uri=http://www.kglad.com/Files/gp/&”;
OAuthREQ += “response_type=token&”;
OAuthREQ +=
“client_id=187572201816.apps.googleusercontent.com”;
urlLoader.addEventListener(Event.COMPLETE,
initLoadCompleteF);
// Initialize authorization request
.initAuth();
}
}
urlLoader.addEventListener(IOErrorEvent.IO_ERROR,loadErrorF);
}
private function initAuth():void{
urlVar.requestURL = OAuthREQ;
urlR.data = urlVar;
urlLoader.load(urlR);
}
private function initLoadCompleteF(e:Event):void{
if(urlLoader.data.indexOf(’A HREF=“’)>-1){
var htmlS:String = urlLoader.data.split(’A HREF=“’)[1]
.split(’”’)[0];
htmlS = htmlS.split(“&”).join(“&”);
navigateToURL(new URLRequest(htmlS),"_self");
} else {
trace(urlLoader.data);
}
}
private function initUI():void{
// Initialize the comboboxes
init_cbF();
// Initialize a help_uri object used to display help
// and assign the uri when one of the comboboxes
// changes
Google+
init_help_uri_objF();
// Initialize the call API button
init_callF();
// Initialize the clear button
init_clearF();
clearF();
}
// the call API listener function
private function APIcallF(uri:String):void{
// All the data sent to google plus is in uri
urlVar.requestURL = uri;
urlR.data = urlVar;
urlLoader.load(urlR);
}
private function loadCompleteF(e:Event):void{
// Data received after calling the google plus API is
// displayed in the received_ta TextArea
receivedF(urlLoader.data);
}
private function loadErrorF(e:Event):void{
receivedF(e.toString());
}
private function receivedF(s:String):void{
received_ta.appendText(“******************** Begin Data
********************\n"+"****** "+resourceS+"/"+methodS+": Input Param = "+input_tf.
text+" ******\n“+s+”\n******************** End Data ********************\n”);
received_ta.verticalScrollPosition =
received_ta.maxVerticalScrollPosition;
}
private function init_cbF():void{
// Labels and change listeners added to the
// three comboboxes
with(people_cb){
addItem({label:“Select an API Call”});
addItem({label:“get”});
addItem({label:“search”});
addItem({label:“listByActivity”});
addEventListener(Event.CHANGE,cbChangeF);
}
with(activities_cb){
addItem({label:“Select an API Call”});
addItem({label:“get”});
addItem({label:“list”});
addItem({label:“search”});
addEventListener(Event.CHANGE,cbChangeF);
529
530
Chapter 11 n Social Gaming: Social Networks
}
with(comments_cb){
addItem({label:“Select an API Call”});
addItem({label:“get”});
addItem({label:“list”});
addEventListener(Event.CHANGE,cbChangeF);
}
}
private function cbChangeF(e:Event):void{
// Clear the Input Parameters textfield
input_tf.text = “”;
// The resource string is derived from the combobox name
resourceS = e.currentTarget.name.split(“_”)[0];
// The method is the label as long as Select an API Call
// is not selected
methodS = e.currentTarget.selectedLabel;
if(methodS.indexOf(“Select”)==-1){
// helpF displays help for calling a
// particular API
helpF(resourceS,methodS);
}
}
// The help_uri_obj is defined. It is an Object that uses
// the resource (people, activites, comments) and the method
// (get, search, list, listByActivity) to store help text and
// function name.
private function init_help_uri_objF():void{
help_uri_obj = {};
help_uri_obj[“people”] = {};
// The second parameter is a function that returns the
// uri string. This allows the most recent input_tf.text
// to be used in the API calls.
help_uri_obj[“people”][“get”] = [“Get a person’s profile. me (the
game player) is used by default. If you want a different profile, enter their user ID (not
name) in the Input Parameter textfield. You can use People/search to find ID’s using
names”,peopleGetF];
help_uri_obj[“people”][“search”] = [“Search all public profiles.
Enter your search term (person’s name) in the Input Parameter textfield.”,peopleSearchF];
help_uri_obj[“people”][“listByActivity”] = [“List all of the
people in the specified collection for a particular activity (post) where collection =
\”resharers\” or \“plusoners\”. In this example plusoners is used. Enter activity ID
in the Input Parameter textfield. Find activity ID’s by using Activities/list.”,
peopleListByActivityF];
help_uri_obj[“activities”] = {};
Google+
help_uri_obj[“activities“][”get“] = [”Get an activity (post) by
id. Enter the activity ID in the Input Parameter textfield. An activity ID can be found
using Activities/list.”,activitiesGetF];
help_uri_obj[“activities”][“list”] = [“List all of the activities
(posts) for a particular user. Enter the user ID in the Input Parameter textfield or the
default \”me\“ (the game player) will be used.”,activitiesListF];
help_uri_obj[“activities”][“search”] = [“Search public activities
(posts). Enter a search string in the Input Parameter textfield.”,activitiesSearchF];
help_uri_obj[“comments”] = {};
help_uri_obj[“comments”][“get”] = [“Get a comment by ID. Find a
comment ID from Comments/list and enter it in the Input Parameter textfield.”,commentsGetF];
help_uri_obj[“comments”][“list”] = [“List all of the comments
for an activity (post) by ID. Enter the activity ID in the Input Parameter textfield. An
activity ID can be found using Activities/list.”,commentsListF];
}
private function helpF(resourceS,methodS):void{
// The first array element of help_uri_obj is the
// help text displayed when a combobox changes.
help_tf.text = help_uri_obj[resourceS][methodS][0];
}
private function init_callF():void{
// The call API button listener
call_btn.addEventListener(MouseEvent.CLICK,callF);
}
private function callF(e:MouseEvent):void{
// The second array element of help_uri_obj is the
// function to be called and returns the google
// plus uri.
var uri:String = help_uri_obj[resourceS][methodS][1]();
//trace(uri);
APIcallF(uri);
}
private function init_clearF():void{
// The clear Received Data TextArea listener
clear_btn.addEventListener(MouseEvent.CLICK,clearF);
}
private function clearF(e:MouseEvent=null):void{
received_ta.text = “”;
}
// The functions called in callF that return the google
// plus uris. the escape function url-encodes strings
// converting, for example, spaces into the url-safe %20
private function peopleGetF():String{
531
532
Chapter 11 n Social Gaming: Social Networks
if(input_tf.text.length<1 || input_tf.text=="me"){
return
"https://www.googleapis.com/plus/v1/people/me?access_token="+access_token;
} else {
return
"https://www.googleapis.com/plus/v1/people/"+escape(input_tf.text)+"?key="+API_KEY;
}
}
private function peopleSearchF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/people?query=glad&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/people?query="+escape(input_tf.text)+
"&maxResults=20"+"&key="+API_KEY;
}
}
private function peopleListByActivityF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities/z12ow5iraufyz5hgw04cc1nzenjrjvlinhg/
people/plusoners?key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities/"+escape(input_tf.text)+"/people/
plusoners?key="+API_KEY;
}
}
private function activitiesGetF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities/z12ow5iraufyz5hgw04cc1nzenjrjvlinhg?
alt=json&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities/"+escape(input_tf.text)+"?alt=
json&key="+API_KEY;
}
}
private function activitiesListF():String{
if(input_tf.text.length<1 || input_tf.text=="me"){
return
"https://www.googleapis.com/plus/v1/people/me/activities/public?access_token=
"+access_token;
} else {
Google+
return
"https://www.googleapis.com/plus/v1/people/"+escape(input_tf.text)+"/activities/
public?alt=json&key="+API_KEY;
}
}
private function activitiesSearchF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities?query=help&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities?query="+escape(input_tf.text)+
"&key="+API_KEY;
}
}
private function commentsGetF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/comments/zUgUkZP-t0npF0jntaPA3G-VBTt6blguPAmJjNBI_LSktqQLLZyqTA5SViPGXU6rJ3Tzu706Zk?key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/comments/"+escape(input_tf.text)+"?key=
"+API_KEY;
}
}
private function commentsListF():String{
if(input_tf.text.length<1){
return
"https://www.googleapis.com/plus/v1/activities/z12ow5iraufyz5hgw04cc1nzenjrjvlinhg/
comments?alt=json&key="+API_KEY;
} else {
return
"https://www.googleapis.com/plus/v1/activities/"+escape(input_tf.text)+"/comments?
alt=json&key="+API_KEY;
}
}
}
}
Creating a Google+ ActionScript 3.0 Library
In /support files/Chapter 11/googlePlus/googlePlus_02 you’ll find the files that separate code for the user interface and code used with Google+. The code used with
Google+ is in the com.kglad.GooglePlus class and the three classes (People,
Activities, and Comments) on which GooglePlus depends.
533
534
Chapter 11 n Social Gaming: Social Networks
I created the library starting with the files in /support files/Chapter 11/googlePlus/
googlePlus_02 and separating user interface code from code needed to interact with
Google+.
The com.kglad.GooglePlus class is a singleton class. Instantiate a
using:
GooglePlus
instance
var gp:GooglePlus = GooglePlus.getInstance(api_key,client_id,redirect_uri);
You must pass your API key. If you need an authentication token, you must pass a
client ID. If you pass a client ID and do not pass a redirect URI, the string in the
address bar is used as the redirect URI.
That won’t be a problem if your redirect URI includes a file name other than index.
Otherwise, that will be a problem (because Google expects an exact match), so you
should explicitly pass a third parameter if you are requesting an authorization token.
If there isn’t an exact match, Google will return a 400 Bad Request error. (See
Figure 11.26
400 Bad Request return from Google when passing http://www.kglad.com/Files/gp/ for the redirect URI when
Google expected http://www.kglad.com/Files/gp. (Note: the only difference between the two is an unexpected
final forward slash.)
Source: Google® Inc.
Figure 11.26.)
The
class has eight main methods (peopleGetF, peopleSearchF, peopleList
activitiesListF, activitiesSearchF, commentsGetF,
commentsListF). There are eight auxillary methods (peopleGet_helpF, peopleSearch_
peopleListByActivity_helpF,
activitiesGet_helpF,
activitiesList_helpF,
helpF,
activitiesSearch_helpF, commentsGet_helpF, commentsList_helpF) that return a string
containing helpful information about using corresponding main methods.
GooglePlus
ByActivityF,
activitiesGetF,
Google+
Figure 11.27
peopleGetF
method signature.
®
Source: Google Inc.
Figure 11.28
peopleSearchF
Source: Google® Inc.
method signature.
535
536
Chapter 11 n Social Gaming: Social Networks
Figure 11.29
peopleListByActivityF
method signature.
Source: Google® Inc.
Figure 11.30
activitiesGetF
Source: Google® Inc.
method signature.
Google+
Figure 11.31
activitiesListF
method signature.
Source: Google® Inc.
Figure 11.32
activitiesSearchF
Source: Google® Inc.
method signature.
537
538
Chapter 11 n Social Gaming: Social Networks
Figure 11.33
commentsGetF
method signature.
Source: Google® Inc.
Figure 11.34
commentsListF
method signature.
Source: Google® Inc.
Figures 11.27 through 11.34 show the eight main methods.
The help documents are in /support files/Chapter 11/googlePlus/googlePlus_02/
GooglePlus/docs.
Open index.html in your browser and click GooglePlus. All the available methods are
listed.
Here is the code used by index.swf in /support files/Chapter 11/googlePlus/
googlePlus_02 that utilizes GooglePlus.swc. The only GooglePlus code is in the Main
Google+
constructor, where GooglePlus.getInstance() is used, and in
public methods of GooglePlus are used.
help_objF,
where all the
com.kglad.Main
package com.kglad {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
public class Main extends MovieClip {
private var help_obj:Object;
private var methodS:String;
private var resourceS:String;
private var gp:GooglePlus;
public function Main() {
//var gp:GooglePlus =
GooglePlus.getInstance(api_key,client_id,redirect_uri);
// The first parameter passed to getInstance is your
// API key and is required.
// The second parameter passed to getInstance is your
// client ID and is optional. If not passed, no
// authorization token will be requested.
// The third parameter passed to getInstances is the
// redirect URI and is optional. If no second parameter
// is passed, there is no need for the redirect URI.
// If a second parameter is passed and no third parameter
// is passed, the URL in the address bar is used for the
// redirect. If that is not exactly what Google+ expects,
// you will receive a 400 Bad Request page from Google.
gp =
GooglePlus.getInstance("AIzaSyA6BBrmHCsL5trpQzj4oIXUBxAkCZwHcWU,187572201816.apps.
googleusercontent.com,http://www.kglad.com/Files/gp/");
initUI();
}
private function initUI():void{
// Initialize the comboboxes
init_cbF();
// Initialize the call API button
init_callF();
// Initialize the clear button
init_clearF();
help_objF();
clearF();
}
private function receivedF(s:String):void{
539
540
Chapter 11 n Social Gaming: Social Networks
received_ta.appendText("******************** Begin Data
********************\n"+"****** "+resourceS+"/"+methodS+": Input Param = "+input_tf.
text+" ******\n"+s+"\n******************** End Data ********************\n");
received_ta.verticalScrollPosition =
received_ta.maxVerticalScrollPosition;
}
private function init_cbF():void{
// Labels and change listeners added to the
// three comboboxes
with(people_cb){
addItem({label:"Select an API Call"});
addItem({label:"get"});
addItem({label:"search"});
addItem({label:"listByActivity"});
addEventListener(Event.CHANGE,cbChangeF);
}
with(activities_cb){
addItem({label:"Select an API Call"});
addItem({label:"get"});
addItem({label:"list"});
addItem({label:"search"});
addEventListener(Event.CHANGE,cbChangeF);
}
with(comments_cb){
addItem({label:"Select an API Call"});
addItem({label:"get"});
addItem({label:"list"});
addEventListener(Event.CHANGE,cbChangeF);
}
}
private function cbChangeF(e:Event):void{
// Clear the Input Parameters textfield
input_tf.text = "";
// The resource string is derived from the combobox name
resourceS = e.currentTarget.name.split("_")[0];
// The method is the label as long as Select an API Call
// is not selected
methodS = e.currentTarget.selectedLabel;
if(methodS.indexOf("Select")==-1){
// helpF displays help for calling a
// particular API
helpF(resourceS,methodS);
}
}
Google+
private function help_objF():void{
help_obj = {};
help_obj["people"] = {};
// The second parameter is a function that returns
// the uri string. This allows the most recent
// input_tf.text to be used in the API calls.
help_obj["people"]["get"] =
[gp.peopleGet_helpF,gp.peopleGetF];
help_obj["people"]["search"] =
[gp.peopleSearch_helpF,gp.peopleSearchF];
help_obj["people"]["listByActivity"] =
[gp.peopleListByActivity_helpF,gp.peopleListByActivityF];
help_obj["activities"] = {};
help_obj["activities"]["get"] =
[gp.activitiesGet_helpF,gp.activitiesGetF];
help_obj["activities"]["list"] =
[gp.activitiesList_helpF,gp.activitiesListF];
help_obj["activities"]["search"] =
[gp.activitiesSearch_helpF,gp.activitiesSearchF];
help_obj["comments"] = {};
help_obj["comments"]["get"] =
[gp.commentsGet_helpF,gp.commentsGetF];
help_obj["comments"]["list"] = [gp.commentsList_helpF,
gp.commentsListF];
}
private function helpF(resourceS:String,methodS:String):void{
help_tf.text = help_obj[resourceS][methodS][0]();
}
private function init_callF():void{
// The call API button listener
call_btn.addEventListener(MouseEvent.CLICK,callF);
}
private function callF(e:MouseEvent):void{
help_obj[resourceS][methodS][1](receivedF,input_tf.text)
}
private function init_clearF():void{
// The clear Received Data TextArea listener
clear_btn.addEventListener(MouseEvent.CLICK,clearF);
}
private function clearF(e:MouseEvent=null):void{
received_ta.text = "";
}
}
541
542
Chapter 11 n Social Gaming: Social Networks
}
Here is the source code for the GooglePlus class and three classes (People,
and Comments) it depends upon.
Activities,
Com.kglad.GooglePlus
package com.kglad{
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.net.URLRequestMethod;
import flash.net.navigateToURL;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLVariables;
import flash.external.ExternalInterface
import flash.display.MovieClip;
public class GooglePlus extends MovieClip{
private static var gp:GooglePlus;
private static var allowInstantiation:Boolean;
private var API_KEY:String;
private var client_id:String;
private var redirect_uri:String;
private var urlR:URLRequest = new URLRequest("gateway.php");
private var urlLoader:URLLoader = new URLLoader();
private var urlVar:URLVariables = new URLVariables;
private var OAuthREQ:String;
private var callbackF:Function;
private var helpS:String;
private var people:People;
private var activities:Activities;
private var comments:Comments;
public static function getInstance(API_KEY:String,client_id:String="",
redirect_uri:String=""):GooglePlus {
if (gp == null) {
allowInstantiation=true;
gp = new GooglePlus(API_KEY,client_id,redirect_uri);
allowInstantiation=false;
}
return gp;
}
public function GooglePlus(_API_KEY:String,_client_id:String,_redirect_
uri:String):void {
Google+
if (! allowInstantiation) {
throw new Error("Error: Instantiation failed:
SingletonDemo.getInstance() instead of new.");
} else {
API_KEY = _API_KEY;
client_id = _client_id;
redirect_uri = _redirect_uri;
people = new People(API_KEY);
activities = new Activities(API_KEY);
comments = new Comments(API_KEY);
if(client_id){
getAuthF();
}
}
}
private function getAuthF():void{
urlR.method = URLRequestMethod.POST;
// Inject JavaScript into the swf’s embedding html file.
// Used to return the embedding html’s url. The url
// contains google’s access token as a query string.
var href_js :XML =
<script>
<![CDATA[
<function(){
return window.location.href;
}
]]>
</script>
var urlS:String = ExternalInterface.call(href_js);
// If not testing in Flash test environment
if(urlS){
// If this is the Google+ callback with
// access_token
if(urlS.split(“access_token=”)[1]){
var access_token:String =
urlS.split(“access_token=”)[1].split(“&”)[0];
people.access_tokenF = access_token;
activities.access_tokenF = access_token;
urlLoader.addEventListener(Event.COMPLETE,loadCompleteF);
} else {
// If this is initial getInstance call and
// client_id passed, request access_token.
if(client_id){
// if no redirect_uri was passed,
Use
543
544
Chapter 11 n Social Gaming: Social Networks
// use current url.
if(!redirect_uri){
redirect_uri = urlS;
}
OAuthREQ =
“https://accounts.google.com/o/oauth2/auth?”;
OAuthREQ +=
“scope=https://www.googleapis.com/auth/plus.me&”;
OAuthREQ += “state=/profile&”;
OAuthREQ += “redirect_uri=“+redirect_uri+”&”;
OAuthREQ += “response_type=token&”;
OAuthREQ += “client_id=”+client_id;
urlLoader.addEventListener(Event.COMPLETE,authLoadCompleteF);
// Initialize authorization request.
initAuth();
}
}
}
urlLoader.addEventListener(IOErrorEvent.IO_ERROR,loadErrorF);
}
private function initAuth():void{
//urlVar.requestURL = REQ1+API_KEY;
urlVar.requestURL = OAuthREQ;
urlR.data = urlVar;
urlLoader.load(urlR);
}
private function authLoadCompleteF(e:Event):void{
if(urlLoader.data.indexOf(’A HREF=“’)>-1){
var htmlS:String = urlLoader.data.split(’A
HREF=“’)[1].split(’“’)[0];
htmlS = htmlS.split(“&”).join(“&”);
navigateToURL(new URLRequest(htmlS),”_self”);
} else {
trace(urlLoader.data);
}
}
// the call API listener function
private function APIcallF(uri:String):void{
// All the data sent to google plus is in uri
urlVar.requestURL = uri;
urlR.data = urlVar;
urlLoader.load(urlR);
}
Google+
private function loadCompleteF(e:Event):void{
// Data received after calling the google plus API
// is returned to the callback function.
callbackF(urlLoader.data);
}
private function loadErrorF(e:Event):void{
trace(e.toString());
}
//////////// people //////////////////
// get
public function peopleGet_helpF():String{
return people.get_helpF();
//return “Get a person’s profile. me (the game player)
// is used by default. If you want a different profile,
// enter their user ID (not name) in the Input Parameter
// textfield. You can use People/search to find ID’s
// using names”;
}
public function peopleGetF(_callbackF:Function,queryS:String=“”):void{
callbackF = _callbackF;
APIcallF(people.getF(queryS));
}
// search
public function peopleSearch_helpF():String{
return people.search_helpF();
}
public function peopleSearchF(_callbackF:Function,queryS:String=“”,
languageS:String=“”,maxResults:int=10):void{
callbackF = _callbackF;
APIcallF(people.searchF(queryS,languageS,maxResults));
}
// list by activity
public function peopleListByActivity_helpF():String{
return people.listByActivity_helpF();
}
public function
peopleListByActivityF(_callbackF:Function,activityID:String=“”,collectionS:
String=“plusoners”,maxResults:int=20):void{
callbackF = _callbackF;
APIcallF(people.listByActivityF(activityID,collectionS,maxResults));
}
//////////// activities //////////////////
// get
545
546
Chapter 11 n Social Gaming: Social Networks
public function activitiesGet_helpF():String{
return activities.get_helpF();
}
public function activitiesGetF(_callbackF:Function,activityID:
String=“”):void{
callbackF = _callbackF;
APIcallF(activities.getF(activityID));
}
// list
public function activitiesList_helpF():String{
return activities.list_helpF();
}
public function activitiesListF(_callbackF:Function,userID:String=“”,
maxResults:int=20):void{
callbackF = _callbackF;
APIcallF(activities.listF(userID,maxResults));
}
// search
public function activitiesSearch_helpF():String{
return activities.search_helpF();
}
public function activitiesSearchF(_callbackF:Function,queryS:String=“”,
languageS:String=“”,maxResults:int=10,orderByS:String=“recent”):void{
callbackF = _callbackF;
APIcallF(activities.searchF(queryS,languageS,maxResults,orderByS));
}
//////////// comments //////////////////
// get
public function commentsGet_helpF():String{
return comments.get_helpF();
}
public function
commentsGetF(_callbackF:Function,commentID:String=“”):void{
callbackF = _callbackF;
APIcallF(comments.getF(commentID));
}
// list
public function commentsList_helpF():String{
return comments.list_helpF();
}
public function commentsListF(_callbackF:Function,activityID:String=“”,
maxResults:int=20,sortOrderS:String=“ascending”):void{
callbackF = _callbackF;
APIcallF(comments.listF(activityID,maxResults,sortOrderS));
}
Google+
}
}
com.kglad.People
package com.kglad {
internal class People {
private var API_KEY:String;
private var access_token:String;
private var uriS:String;
public function People(_API_KEY:String) {
API_KEY = _API_KEY;
}
// get
internal function get_helpF():String{
return “Get a person’s profile. me (the game player) is used
by default. If you want a different profile, pass their user ID (not name) to the
peopleGetF method of your GooglePlus method. You can use the peopleSearchF method
to find ID’s using names”;
}
internal function getF(queryS:String):String{
if(!queryS || queryS==“me”){
uriS =
“https://www.googleapis.com/plus/v1/people/me?access_token=”+access_token;
} else {
uriS =
“https://www.googleapis.com/plus/v1/people/”+escape(queryS)+“?key=”+API_KEY;
}
return uriS;
}
// search
internal function search_helpF():String{
return “Search all public profiles. Pass your search term
(person’s name) to the peopleSearchF method. You can also pass optional language and
maximum results (default = 10) parameters.”;
}
private function searchParamsF(uriS:String,languageS:String,
maxResults:int):String{
if(languageS){
uriS = uriS+“&language=”+languageS;
}
if(maxResults!=10){
uriS = uriS+“&maxResults=”+maxResults;
}
547
548
Chapter 11 n Social Gaming: Social Networks
return uriS;
}
internal function
searchF(queryS:String,languageS:String,maxResults:int):String{
uriS = “https://www.googleapis.com/plus/v1/people?query=
”+escape(queryS)+“&maxResults=20”+“&key=”+API_KEY;
uriS = searchParamsF(uriS,languageS,maxResults);
return uriS;
}
// listByActivity
internal function listByActivity_helpF():String{
return “List all of the people in the specified collection for
a particular activity/post. Pass the activity ID (use the activitiesListF method
to find activity IDs), an optional collection parameter (either \“resharers\” or
\“plusoners\”, default = \“plusoners\”) and an optional maximum results parameter
(default = 20).”;
}
private
function
listByActivityParamsF(uriS:String,collectionS:
String,maxResults:int):String{
if(collectionS!=“plusoners”){
uriS = uriS+“&collection=”+collectionS;
}
if(maxResults!=20){
uriS = uriS+“&maxresults=”+maxResults;
}
return uriS;
}
internal function
listByActivityF(activityID:String,collectionS:String,maxResults:int):String{
uriS = “https://www.googleapis.com/plus/v1/activities/”+escape
(activityID)+“/people/”+collectionS+“?key=”+API_KEY;
uriS = listByActivityParamsF(uriS,collectionS,maxResults);
return uriS;
}
internal function set access_tokenF(_access_token:String):void{
access_token = _access_token
}
}
}
com.kglad.Activities
package com.kglad {
Google+
internal class Activities {
private var API_KEY:String;
private var access_token:String;
private var uriS:String;
public function Activities(_API_KEY:String) {
API_KEY = _API_KEY;
}
// get
internal function get_helpF():String{
return “Get an activity (post) by id. Pass the activity ID to the
activitiesGetF method. An activity ID can be found using the activitiesListF method.”;
}
internal function getF(activityID:String):String{
return
“https://www.googleapis.com/plus/v1/activities/
“+escape(activityID)+”?alt=json&key=”+API_KEY;
}
// list
internal function list_helpF():String{
return “List all of the activities/posts for a particular user.
Pass the user ID to the activitiesListF method or the default \“me\” (the game player)
will be used. An optional maximum results (default = 20) parameter may also be
passed.”;
}
private function listParamsF(uriS:String,maxResults:int):String{
if(maxResults!=20){
uriS = uriS+“&maxresults=”+maxResults;
}
return uriS;
}
internal function listF(userID:String,maxResults:int):String{
if(!userID || userID==“me”){
uriS = “https://www.googleapis.com/plus/v1/people/me/
activities/public?access_token=”+access_token;
} else {
uriS
=
“https://www.googleapis.com/plus/v1/people/
“+escape(userID)+”/activities/public?alt=json&key=”+API_KEY;
}
uriS = listParamsF(uriS,maxResults);
return uriS;
}
// search
internal function search_helpF():String{
return “Search public activities/posts. Pass a search string to
the activitiesSearchF method. Option parameters language, maximum results (default =
549
550
Chapter 11 n Social Gaming: Social Networks
10) and order by (\“best\” or \“recent\” are acceptable, default = \“recent\”) may also
be passed.”;
}
private function searchParamsF(uriS:String,languageS:String,
maxResults:int,orderByS:String):String{
if(languageS){
uriS = uriS+“&language=”+languageS;
}
if(maxResults!=10){
uriS = uriS+“&maxResults=”+maxResults;
}
if(orderByS!=“recent”){
uriS = uriS+“&orderBy”+orderByS;
}
return uriS;
}
internal function searchF(queryS:String,languageS:String,maxResults:
int,orderByS:String):String{
if(!queryS){
uriS = “https://www.googleapis.com/plus/v1/activities?
query=help&key=”+API_KEY;
} else {
uriS = “https://www.googleapis.com/plus/v1/activities?
query=”+escape(queryS)+“&key=”+API_KEY;
}
uriS = searchParamsF(uriS,languageS,maxResults,orderByS);
return uriS;
}
internal function set access_tokenF(_access_token:String):void{
access_token = _access_token
}
}
}
Com.kglad.Comments
package com.kglad {
internal class Comments {
private var API_KEY:String;
private var uriS:String;
public function Comments(_API_KEY:String) {
API_KEY = _API_KEY;
}
// get
internal function get_helpF():String{
Google+
return “Get a comment by ID. Find a comment ID by calling the
commentsListF method.”;
}
internal function getF(commentID:String):String{
uriS = “https://www.googleapis.com/plus/v1/comments/”+escape
(commentID)+“?key=”+API_KEY;
return uriS;
}
// list
internal function list_helpF():String{
return “List all of the comments for an activity (post) by ID.
Pass the activity ID to the activitiesListF method. Optional parameters maximum results
(default = 20) and sort order (\“ascending\” - oldest first or \“descending\” - newest
first, default = \“ascending\”) may also be passed. An activity ID can be found using
the activitiesListF method.”;
}
private function listParamsF(uriS:String,maxResults:int,sortOrderS:
String):String{
if(maxResults!=20){
uriS = uriS+“&maxResults=”+maxResults;
}
if(sortOrderS!=“ascending”){
uriS = uriS+“&sortOrder”+sortOrderS;
}
return uriS;
}
internal function listF(activityID:String,maxResults:int,sortOrderS:
String):String{
uriS = “https://www.googleapis.com/plus/v1/activities/”+escape
(activityID)+“/comments?alt=json&key=”+API_KEY;
uriS = listParamsF(uriS,maxResults,sortOrderS);
return uriS;
}
}
}
Creating a Google+ SWC from the Google+ ActionScript 3.0 Library
In /support files/Chapter 11/googlePlus/googlePlus_03/GooglePlus you’ll find
GooglePlus.swc, which contains the compiled code from the Google+ ActionScript
3.0 library in /support files/Chapter 11/googlePlus/googlePlus_02.
To create an SWC in Flash Pro CS6, create a new FLA (for example, swcFLA.fla) and
save it in the directory with access to the classes you want to compile into an SWC.
551
552
Chapter 11 n Social Gaming: Social Networks
In that FLA, create an empty MovieClip (for example, by clicking Insert > New
Symbol > MovieClip), check Export for ActionScript, assign the class you want to
compile into the SWC (for example, com.kglad.GooglePlus), and click OK.
Finally, right-click the Library MovieClip, click Export SWC File, assign your SWC’s
file name (for example, GooglePlus.swc), and click Save. You now have a convenient
way to share and use your code.
The only thing left to do is to create documentation for your library. I used Ortelius
(http://ortelius.marten.dk/sider/as-documentation-generator_27.aspx).
To use this SWC, add it to your SWF’s library path (File > Publish Settings > Flash >
ActionScript Settings > Library path). You can then use the public methods of this
GooglePlus class.
The source files are in /support files/Chapter 11/googlePlus/googlePlus_03/GooglePlus/
src. The help documents are in /support files/Chapter 11/googlePlus/googlePlus_03/
GooglePlus/docs.
Open index.html in your browser and click GooglePlus. All the available methods
will be listed.
Chapter 12
Social Gaming: Multiplayer
Games
With multiplayer games, data needs to be communicated among the players. When a
player makes a move (changing the game state), the updated game state needs to be
communicated to all the other players. In addition, that communication needs to
occur in a timely manner.
With turn-based games (such as card games), that communication among players
can take as long as a few seconds without degrading the game experience. With
real-time games (such as shooter games), even a 250-millisecond delay in communicating game state leads to a significantly degraded player experience. Consequently,
you need substantial expertise to successfully develop and deploy real-time multiplayer games.
There are two fundamentally different ways to accomplish communication among
players. Players can communicate via a server (in server-based games), or they can
communicate directly from player to player (in peer-to-peer games).
Server-Based Multiplayer Games
Generally, the code in each player’s Flash game handles the player’s input, transmits
player data to the server, receives other players’ data, and displays the game state.
The server receives player data, validates the data, updates and maintains game
state, and transmits each player’s data to the other players.
The code used on the server cannot be ActionScript, so you will need to learn a
server-side coding language, such as PHP or C#. Server-side coding is beyond the
scope of this book, so I won’t cover server-based multiplayer games except to say
553
554
Chapter 12 n Social Gaming: Multiplayer Games
that you need to have advanced coding skills in at least two languages (ActionScript
and a server-side language) to create these game types.
Peer-to-Peer Games
Since Flash Player 10, you can create multiplayer games without needing an intermediary server to facilitate player communication. The Flash Player can use a protocol
(Adobe’s Real-Time Media Flow Protocol) that allows direct peer-to-peer
communication.
Instead of using server-side code to handle the game logic and coordinate game state
among players, each peer in the network handles its own game logic and game state
and communicates that directly to its peers, and each peer updates its game state based
on the data received from others.
To use peer-to-peer networking, each peer must connect with an Adobe server. Peerto-peer communication doesn’t go through that server (or it would not be peerto-peer), but peers must stay connected with the Adobe server to communicate with
each other.
Note: This does not use Adobe Media Server. The peer-to-peer communication is done
via the Adobe cirrus server (http://labs.adobe.com/wiki/index.php/Cirrus:FAQ).
To communicate with the Adobe server, you should use your own server URL and
developer key. You can obtain that URL and key at www.adobe.com/cfusion/entitlement/
index.cfm?e=cirrus.
Following is a simple tic-tac-toe game that uses Adobe’s peer-to-peer networking to
pair up players. The NetConnection class establishes a connection to the Adobe server,
while the NetGroup class is used for peer-to-peer communication.
I used only a small part of the NetGroup methods for the tic-tac-toe game, but there
are more available if you’re sharing data among many users and/or sharing large
amounts of data.
The tic-tac-toe game is in /support files/Chapter 11/multiplayer and is extensively
commented. Main contains the game logic code and the code that allows each player
to pair with an opponent and send and receive data from their opponent.
Main.as
package com.kglad{
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.display.Sprite;
Peer-to-Peer Games
import
import
import
public
flash.utils.getDefinitionByName;
flash.utils.Timer;
flash.events.TimerEvent;
class Main extends MovieClip {
private var obj:Object = {};
private var messageObj:Object;
private var initX:int = 10;
private var initY:int = 100;
private var markerP:Sprite;
private var boardP:Sprite;
private var MarkerS:String;
// Booleans used to pair group members.
private var hsInitialAccept:Boolean;
private var startGameCalled:Boolean
private var refuseConnections:Boolean;
private var hs1Timer:Timer;
private var hs1ReceiveTime:int = 0;
private var gameA:Array = [];
private var markerCountA:Array;
private var newGameTimer:Timer;
public function Main() {
// Timer used to delay starting a new game after
// previous completed.
newGameTimer = new Timer(3000,1);
newGameTimer.addEventListener(TimerEvent.TIMER, startF);
// Timer used to repeatedly attempt to connect with
// another peer. At each step of the pairing attempt,
// the timer is reset to wait some time (10 seconds is
// plenty of time) for the next response.
hs1Timer = new Timer(10000,1);
hs1Timer.addEventListener(TimerEvent.TIMER,hs1F);
// Initialize peer-to-peer (P2P) connection. Except
// for peer-to-peer connection housekeeping, all
// P2P received data are sent to receivedDataF
// below
P2P.init(this);
// Initialize tic-tac-toe board
setUpF();
// Initialize sendName_btn and sendMessage_btn
btnsF();
// Clear carriage returns from the textfields.
message_tf.text = “”;
sendMessage_tf.text =“”;
chat_tf.text = “”;
555
556
Chapter 12 n Social Gaming: Multiplayer Games
}
private function setUpF():void{
// Create a parent for the markers (X’s and O’s)
markerP = new Sprite();
// Create a parent for the tic-tac-toe board
boardP = new Sprite();
// If the board (when it is enabled) is clicked,
// add a marker (X or O)
boardP.addEventListener(MouseEvent.CLICK,markerF);
boardP.buttonMode = true;
boardP.mouseChildren = false;
boardP.mouseEnabled = false;
addChild(boardP);
addChild(markerP);
// Add Square instances to boardP
for(var col:int = 0;col<3;col++){
for(var row:int = 0;row<3;row++){
var sq:Square = new Square();
boardP.addChild(sq);
sq.col = col;
sq.row = row;
sq.x = initX+sq.width*col;
sq.y = initY+sq.height*row;
}
}
}
private function initGameAF():void{
// gameA contains a zero if there’s no marker at the
// corresponding tic-tac-toe board position.
// This is used to determine when a game is won;
// i.e., if there are three of this peer’s markers
// in a row, column, or diagonal.
gameA = [];
for(var row:int=0;row<3;row++){
gameA[row] = [0,0,0];
}
}
private function btnsF():void{
sendName_btn.addEventListener(MouseEvent.CLICK,sendNameF);
sendName_btn.buttonMode = true;
sendName_btn.mouseChildren = false;
// When at least one other peer connects,
// sendName_btn is made visible. A peer must
// send their name before they are allowed
Peer-to-Peer Games
// to join another peer and start a game.
sendName_btn.visible = false;
sendMessage_btn.addEventListener(MouseEvent.CLICK,sendMessageF);
sendMessage_btn.buttonMode = true;
sendMessage_btn.mouseChildren = false;
// When game starts, sendMessage_btn is made
// visible, allowing peer to chat with their
// paired peer.
sendMessage_btn.visible = false;
disconnect_btn.addEventListener(MouseEvent.CLICK,disconnectF);
disconnect_btn.buttonMode = true;
disconnect_btn.mouseChildren = false;
// When game starts, disconnect_btn is made visible,
// allowing user to disconnect.
disconnect_btn.visible = false;
}
private function sendNameF(e:MouseEvent):void{
// disable sendName_btn if enough text is entered
// into name_tf and send initial data trying to
// pair up with another peer.
if(name_tf.text.length>1){
// Assign Data.nameS.
Data.nameS = name_tf.text
// Cannot change name.
sendName_btn.visible = false;
message_tf.text = “Please wait. Opponent being sought.
This may take a few minutes.\n”
// The first call (by this peer) to hs1F
// (handshake1 starts the conversation to
// pair up and start a game).
hs1F();
} else {
textF(“You must enter more than one character into the name
field”,message_tf);
}
}
private function disconnectF(e:MouseEvent):void{
// An intentional disconnect bans further connections
// between this peer and the disconnected peer.
Data.bannedAddressF = Data.connectionAddressF;
// checkGroupLostF is also called when a group member
// disconnects (by closing their game). If the
// disconnected peer was the peer with whom this
// peer was paired, the pairing process is
557
558
Chapter 12 n Social Gaming: Multiplayer Games
// restarted.
checkGroupLostF(Data.connectionAddressF);
}
internal function textF(txt:String,tf:TextField):void {
// stripF removes initial and trailing white space.
tf.appendText(stripF(txt)+“\n”);
tf.scrollV = tf.maxScrollV;
}
// hs1F (handshake 1 function) where initial data is
// sent trying to pair up with another peer.
internal function hs1F(e:TimerEvent=null):void{
if(Data.nameS.length>1 && !startGameCalled){
if(e){
// When hs1F is called by the hs1Timer
// listener (i.e., when e is not null),
// a different peer (if there is one in
// the group) is selected to receive
// the message.
Data.connectionAddressUpdateF();
}
// hsInitialAccept is used to allow/disallow
// responses to received hs1 and hs2 data.
// I am only responding to one or the other
// (whichever is received first).
hsInitialAccept = true;
startGameCalled = false;
refuseConnections = false;
// messageObjF creates a new Object (thereby
// eliminating data associated with any
// previous messageObj). The NetGroup class
// has methods that transmit objects (and
// their properties).
messageObj = messageObjF();
// Each messageObj has a type, which the recipient
// uses to determine what type of message they are
// receiving.
messageObj.typeS = “hs1”;
// Some messageObjs have a value. This one does not.
// P2P.sendDataF sends messageObj
if(Data.connectionAddressF.length>0){
// Direct this message to one group member.
messageObj.destination = Data.connectionAddressF;
P2P.sendDirectedDataF(messageObj);
}
Peer-to-Peer Games
// Start timer. If no response to this message
// is received within 10 seconds,
hs1TimerResetF(10000);
}
}
// When intentionally disconnecting from a paired
// user and when a connection is lost,
// checkGroupLostF is called.
internal function checkGroupLostF(s:String):void{
if(Data.connectionAddressF == s){
// No longer connected to paired user,
// remove game markers and restart the
// pairing polling.
removeMarkersF();
sendMessage_btn.visible = false;
startGameCalled = false;
refuseConnections = false;
textF(“Your opponent disconnected. Searching for new
opponent”,message_tf);
Data.connectionAddressUpdateF();
hs1F();
}
}
// All messageObj instances start with a base of data
// returned by messageObjF
private function messageObjF():Object{
obj = new Object();
// Each obj has an identifiable sender and
// possibly recipient (Data.connectionIDF).
// Data.id is an (almost) unique identifier
// for each user. Data.connectionIDF is the
// connected user’s Data.id.
obj.id = Data.id;
// Data.connectionIDF returns 0 before a
// tentative pairing is made.
obj.connectionID = Data.connectionIDF;
// User’s name is also sent. This is not unique
// and is not part of the handshake process.
obj.nameS = Data.nameS;
return obj;
}
// The hs1Timer is set/reset here.
private function hs1TimerResetF(n:int):void{
if(!startGameCalled){
559
560
Chapter 12 n Social Gaming: Multiplayer Games
hs1Timer.reset();
hs1Timer.delay = n;
hs1Timer.start();
}
}
// The function that receives all (except the disconnect
// messages) the non-housekeeping data from P2P.
internal function receiveDataF(obj:Object):void{
// when CONFIG::DEBUG is true, all the data in
// obj is displayed in message_tf
CONFIG::DEBUG{
textF(“****** receiveDataF begin: ******”,message_tf);
stringifyF(obj);
textF(“****** receiveDataF end: ******”,message_tf);
}
// Do not respond to any message unless a valid
// name has been stored in Data.nameS
if(Data.nameS.length<1){
return;
}
// When a game is started with another peer (i.e.,
// a pairing is completed), refuseConnections is true.
// This prevents this peer from responding to all
// messages unless they are from the paired
// peer (i.e., when obj.from=Data.connectionAddressF).
if(refuseConnections && obj.from != Data.connectionAddressF){
messageObj = messageObjF();
// hs0 is a connection refusal.
messageObj.typeS = “hs0”;
CONFIG::DEBUG{
textF(“hs0 sent to address:”+obj.from,message_tf);
}
// Direct message to sender
messageObj.destination = obj.from;
P2P.sendDirectedDataF(messageObj);
return;
}
switch(obj.message.typeS){
// hs0 received.
case “hs0”:
// If an hs0 is received from
// Data.connectAddressF, try
// another peer.
if(obj.from==Data.connectionAddressF){
Peer-to-Peer Games
hs1TimerResetF(100);
}
break;
case “hs1”:
// hs1 received.
CONFIG::DEBUG{
textF(“hs1 message received from address:
“+obj.from+”. name: ”+obj.message.nameS,message_tf);
}
if(hsInitialAccept && Data.nameS.length>1){
// Accept hs1 or hs2, whichever
// arrives first after Data.nameS
// is assigned (which is also
// when this peer sent an hs1).
hsInitialAccept = false;
// Allow 10 seconds for response
// to this about-to-be-sent hs2
// message.
hs1TimerResetF(10000);
// Tentatively pair-up with this
// hs1 sending peer.
Data.connectionAddressF = obj.from;
Data.connectionIDF = obj.message.id;
Data.foeNameS = obj.message.nameS;
// Must initialize messageObj
// here and not before the Data
// assignments. (Check
// messageObjF to see if
// Data.connectionIDF is used.)
messageObj = messageObjF();
messageObj.destination = obj.from;
messageObj.typeS = “hs2”;
// “yes” is sent if hs1 accepted and
// Data.nameS has been assigned.
messageObj.valueS = “yes”;
} else {
messageObj = messageObjF();
messageObj.destination = obj.from;
messageObj.typeS = “hs2”;
// Otherwise, hsInitialAccept remains
// true, no tentative pairing is made,
// and “no” is sent.
messageObj.valueS = “no”;
}
561
562
Chapter 12 n Social Gaming: Multiplayer Games
P2P.sendDirectedDataF(messageObj);
break;
case “hs2”:
if(hsInitialAccept){
if(obj.message.valueS==“yes”){
// The hs2 sender has made
// a tentative pairing with
// this peer.
hsInitialAccept = false;
hs1TimerResetF(10000);
Data.connectionAddressF = obj.from;
Data.connectionIDF = obj.message.id;
Data.foeNameS = obj.message.nameS;
// Again, must initialize
// messageObj here and not
// before the Data assignments.
messageObj = messageObjF();
messageObj.destination = obj.from;
messageObj.typeS = “hs3”;
// Confirm pairing
messageObj.valueS = “yes”;
} else {
messageObj = messageObjF();
// Confirm rejection
messageObj.valueS = “no”;
hs1TimerResetF(10000);
}
} else {
messageObj = messageObjF();
messageObj.destination = obj.from;
messageObj.typeS = “hs2”;
// Reject pairing
messageObj.valueS = “no”;
}
P2P.sendDirectedDataF(messageObj);
break;
case “hs3”:
if(obj.message.valueS==“yes”){
// Confirm pairing
messageObj = messageObjF();
messageObj.destination = obj.from;
messageObj.typeS = “hs4”;
hs1TimerResetF(10000);
P2P.sendDirectedDataF(messageObj);
Peer-to-Peer Games
} else {
hs1TimerResetF(10000);
}
break;
case “hs4”:
hs1TimerResetF(10000);
break;
// The messages below relate to chatting and
// game play. Obviously, most of this coding
// is to facilitate peer pairing.
// A chat message.
case “message”:
textF(obj.message.nameS+“:
”+obj.message.valueS,chat_tf);
break;
// An opponent move
case “move”:
// Update the board so this peer’s board
// reflects opponent’s move.
positionMarkerF(obj.message.valueS);
// Allow this peer to make move
boardP.mouseEnabled = true;
textF(“It is your move.”,message_tf);
break;
case “tie game”:
// Tie game.
textF(“Tie Game!\nStarting next game.”,message_tf);
// Switch X and O so players alternate
// who goes first.
switchXO();
// Start the new game after 3-second delay.
newGameTimer.start();
break;
case “loss”:
textF(“You lost!\nStarting next game.”,message_tf);
switchXO();
newGameTimer.start();
break;
}
CONFIG::DEBUG{
563
564
Chapter 12 n Social Gaming: Multiplayer Games
textF(obj.message.id+“==?”+Data.connectionIDF+” ::
”+obj.message.connectionID+“==?”+Data.id+” . name=“+obj.message.nameS+”. bool:
“+startGameBoolF(obj),message_tf);
textF((obj.message.id==Data.connectionIDF)+” ::
“+(obj.message.connectionID==Data.id)+” . name
length>1:“+(obj.message.nameS.length>1)+”. startGameBoolF:
“+startGameBoolF(obj),message_tf);
}
// Final part of pairing process done in
// startGameBoolF
if(!startGameCalled && startGameBoolF(obj)){
// Only want to do this once per pairing
startGameCalled = true;
// And start game (for first time).
initStartF(obj);
}
}
// Check if message sender is this peer’s tentative pair.
private function startGameBoolF(obj:Object):Boolean{
if(obj.message.id==Data.connectionIDF &&
obj.message.connectionID==Data.id){
// If yes, tentative pair is no longer
// tentative. This peer is paired and
// will refuse communication attempts
// by peers other than their paired peer.
refuseConnections = true;
return true;
} else {
return false;
}
}
// Some preliminaries for the first time a game is started.
// After a game is complete, startF() is called for
// subsequent game starts.
private function initStartF(obj:Object):void{
// No need to check for another peer for
// possible pairing.
hs1Timer.stop();
// Make the send message (chat) and disconnect
// buttons visible.
sendMessage_btn.visible = true;
disconnect_btn.visible = true;
// Smallest id is X and moves first. Both players
// will agree on who has the smaller id.
if(Data.id<Data.connectionIDF){
Peer-to-Peer Games
MarkerS = “X”;
} else {
MarkerS = “O”;
}
message_tf.text = “You have an opponent: “+Data.foeNameS+”\n”;
// Start game.
startF();
CONFIG::DEBUG{
textF(“START GAME”,message_tf);
}
}
// Send a chat message.
private function sendMessageF(e:MouseEvent):void{
if(sendMessage_tf.text.length>1){
messageObj = messageObjF();
messageObj.typeS = “message”;
messageObj.valueS = sendMessage_tf.text;
sendMessage_tf.text = “”;
textF(Data.nameS+“: ”+messageObj.valueS,chat_tf);
P2P.sendDataF(messageObj);
}
}
// Called when the board is clicked and a marker needs
// to be added.
private function markerF(e:MouseEvent):void{
// Check which board square was clicked.
for(var i:int = 0;i<boardP.numChildren;i++){
if(boardP.getChildAt(i).hitTestPoint(boardP.mouseX,boardP.mouseY)){
var sq:MovieClip = MovieClip(boardP.getChildAt(i));
// Position a marker over that square.
positionMarkerF([MarkerS,sq.x,sq.y]);
// Update gameA, which is used to determine
// if this peer has won (in gameOverCheckF).
// Each peer checks if they have won after
// their own move.
gameA[sq.row][sq.col] = 1;
break;
}
}
// Disallow adding another marker (until the next turn).
boardP.mouseEnabled = false;
// Send a “move” message indicating this peer’s
// move so opponent can update their board.
messageObj = messageObjF();
565
566
Chapter 12 n Social Gaming: Multiplayer Games
messageObj.typeS = “move”;
messageObj.valueS =
[MarkerS,boardP.getChildAt(i).x,boardP.getChildAt(i).y];
messageObj.destination = Data.connectionAddressF;
P2P.sendDirectedDataF(messageObj);
// Check if game is over.
if(!gameOverCheckF()){
textF(“Please wait for “+Data.foeNameS+”’s
move”,message_tf);
}
}
// This is called when opponent sends a “move” message
// and when this player clicks the board to place a
// marker.
private function positionMarkerF(a:Array):void{
// marker string (“X” or “O“).
var mS:String = a[0];
// x and y positions
var xPos:int = a[1];
var yPos:int = a[2];
// Get a class reference from the marker string
var ClassRef:Class = Class(getDefinitionByName(mS));
// Create a new marker of the correct class (X or O).
var marker:MovieClip = new ClassRef();
// Add the marker to the marker parent.
markerP.addChild(marker);
// And position it.
marker.x = xPos;
marker.y = yPos;
}
// Check for a won or tie game.
private function gameOverCheckF():Boolean{
// No sense checking for a won game before there
// are 5 markers.
if(markerP.numChildren>=5){
// Count how many of this player’s markers
// are in the first row, second row, third
// row, first column, second column,
// third column, top-left to bottom-right
// diagonal, top-right to bottom-left
// diagonal. If 3 markers in any of these
// rows, columns, or diagonals, this
// peer wins.
markerCountA=[0,0,0,0,0,0,0,0];
Peer-to-Peer Games
for (var col:int=0; col<3; col++) {
// Top-left to bottom-right diagonal
markerCountA[6]+=gameA[col][col];
// Top-right to bottom-left diagonal
markerCountA[7]+=gameA[col][2-col];
for (var row:int=0; row<3; row++) {
// Row totals.
markerCountA[row]+=gameA[row][col];
// Column totals.
markerCountA[3+col] += gameA[row][col];
}
// Check if 3 markers in any one row
// or column or diagonal
if(markerCountA.indexOf(3)>-1){
textF(“You won!\nStarting next
game.”,message_tf);
// Let opponent know they lost.
messageObj = messageObjF();
messageObj.typeS = “loss”;
P2P.sendDataF(messageObj);
// switch X and O.
switchXO();
// Start the next game.
newGameTimer.start();
return true;
}
}
}
// If this player did not win and there are nine
// markerP children, this is a tie game.
if(markerP.numChildren==9){
textF(“Tie Game!\nStarting next game.”,message_tf);
// Let opponent know tie game.
messageObj = messageObjF();
messageObj.typeS = “tie game”;
P2P.sendDataF(messageObj);
// switch X and O.
switchXO();
// Start the next game.
newGameTimer.start();
return true;
}
return false;
}
567
568
Chapter 12 n Social Gaming: Multiplayer Games
// Switch X and O
private function switchXO():void{
if(MarkerS==“X“){
MarkerS=“O“;
} else {
MarkerS=“X“;
}
}
// Start the game
private function startF(e:TimerEvent=null):void{
// Initialize gameA (used to determine winner
// in gameOverCheckF);
initGameAF();
// Remove markers from previous game (if there
// was a previous game).
removeMarkersF();
// X always goes first.
if(MarkerS==“X“){
textF(“You go first and are X. Make your move.”,message_tf);
boardP.mouseEnabled = true;
} else {
textF(Data.foeNameS+” goes first. You are O. Please wait
for “+Data.foeNameS+“’s move.”,message_tf);
boardP.mouseEnabled = false;
}
}
// Remove all markers.
private function removeMarkersF():void{
for(var i:int=markerP.numChildren-1;i>=0;i–){
markerP.removeChildAt(i);
}
}
// Strip leading and trailing white space.
private function stripF(s:String):String {
if(s.length==0){
return s;
}
while (s.indexOf(” “)==0 || s.indexOf(“\n“)==0 ||
s.indexOf(“\r“)==0 || s.indexOf(“\t“)==0) {
s=s.substr(1);
}
while (s.lastIndexOf(” “)==s.length-1 ||
s.lastIndexOf(“\n“)==s.length-1 || s.lastIndexOf(“\r“)==s.length-1 ||
s.lastIndexOf(“\t“)==s.length-1) {
Peer-to-Peer Games
s=s.substr(0,−1);
}
return s;
}
// This is used for debugging only
internal function stringifyF(obj:Object):void{
for(var s:* in obj){
if(obj[s] is String){
textF(“obj[“+s+“]=“+obj[s],message_tf);
} else {
textF(s+“:”,message_tf);
stringifyF(obj[s]);
}
}
}
}
}
The P2P class establishes the peer-to-peer connection. The NetGroup class is essential
to establishing an Adobe peer-to-peer connection.
P2P.as
package com.kglad{
import flash.display.MovieClip
import flash.events.NetStatusEvent;
import flash.net.NetConnection;
import flash.net.NetGroup;
import flash.net.GroupSpecifier;
import flash.net.NetGroupReplicationStrategy;
public class P2P {
private static var nc:NetConnection;
private static var netGroup:NetGroup;
private static var main:MovieClip;
internal static function init(_main:MovieClip):void {
main = _main;
// Create a NetConnection instance
nc = new NetConnection();
// Add event listener to handle connections and data
// to and from this peer.
nc.addEventListener(NetStatusEvent.NET_STATUS,netStatusF);
// Use your server address and key (from Adobe,
//http://www.adobe.com/cfusion/entitlement/index.cfm?e=cirrus)
569
570
Chapter 12 n Social Gaming: Multiplayer Games
nc.connect(Data.SERVER+Data.DEVKEY);
}
private static function netStatusF(e:NetStatusEvent):void {
CONFIG::DEBUG {
main.textF(“p2p netStatusF begin:”,main.message_tf);
main.stringifyF(e.info);
main.textF(“p2p netStatusF end:”,main.message_tf);
}
switch (e.info.code) {
case “NetConnection.Connect.Success“:
// When this user connects, initialize group
initializeGroupF();
break;
case “NetGroup.Neighbor.Connect“:
// only ~14 closest peers will be neighbors
Data.addToGroup(e.info.neighbor);
CONFIG::DEBUG{
main.textF(“*** addToGroup:
***“+e.info.neighbor,main.message_tf);
}
// Notify user they need to enter a name.
if(main.message_tf.text.indexOf(“To start, enter your
name and click SEND“)==-1 && Data.nameS.length<1){
main.textF(“To start, enter your name and
click SEND”,main.message_tf);
main.sendName_btn.visible = true;
}
break;
case “NetGroup.Neighbor.Disconnect“:
// A peer connection was lost. Remove from
// Data.groupAddressA and check if peer was
// this user’s opponent.
Data.removeFromGroup(e.info.neighbor);
main.checkGroupLostF(e.info.neighbor);
break;
case “NetGroup.SendTo.Notify“:
CONFIG::DEBUG{
main.textF(“*** NOTIFY ***
“+Data.groupAddressA.length,main.message_tf);
}
// Process received data in Main.
main.receiveDataF(e.info);
// Allowing netGroup.sendToNearest() to
// execute crashes the debug flash player.
Peer-to-Peer Games
// I could not find a problem commenting
// out this section. but I only
// tested with up to 40 peers.
/*
if(e.info.fromLocal){
main.receiveDataF(e.info);
} else {
netGroup.sendToNearest(e.info.message,
e.info.message.destination);
}
*/
break;
}
}
private static function initializeGroupF():void {
// Define a GroupSpecifier. Presumably, using this
// should allow each developer (with his one key)
// to develop many different games and applications.
// This is the tic-tac-toe group.
var groupSpecifier:GroupSpecifier=new GroupSpecifier(“tttGroup“);
// Enable posting to entire group.
groupSpecifier.postingEnabled = true;
// Enable direct routing of a peer.
groupSpecifier.routingEnabled = true;
// Allow peers to join the group automatically.
groupSpecifier.serverChannelEnabled = true;
// Create NetGroup instance.
netGroup=new
NetGroup(nc,groupSpecifier.groupspecWithAuthorizations());
netGroup.addEventListener(NetStatusEvent.NET_STATUS,netStatusF);
}
internal static function sendDataF(obj:Object):void {
if(Data.connectionAddressF.length>0){
// communicate only with the peer at
// Data.connectionAddressF
obj.destination = Data.connectionAddressF;
netGroup.sendToNearest(obj, obj.destination);
} else {
netGroup.sendToAllNeighbors(obj);
}
}
internal static function sendDirectedDataF(obj:Object):void {
// Send message to only one peer.
netGroup.sendToNearest(obj, obj.destination);
571
572
Chapter 12 n Social Gaming: Multiplayer Games
}
// I don’t need this function, but it shows how to send
// a message to everyone in the group.
internal static function sendDataToAllF(obj:Object):void{
netGroup.sendToAllNeighbors(obj);
}
}
}
The Data class is used to store and furnish data needed to establish the peer-to-peer
connection and identify the opponent.
Data.as
package com.kglad {
public class Data {
// These two variables/constants you should get at:
// http://www.adobe.com/cfusion/entitlement/index.cfm?e=cirrus
internal static const
SERVER:String=“rtmfp://p2p.rtmfp.net/fc7d4c8286fa66c1f44daccd-c751a3a86d49/“;
internal static const DEVKEY:String=“fc7d4c8286fa66c1f44daccdc751a3a86d49“;
// Array of connected addresses. One will be used to pair.
internal static var groupAddressA:Array = [];
// Address of pair connection.
private static var connectionAddress:String = “”;
// id of pair connection.
private static var connectionID:Number = 0;
// Banned or disconnected addresses.
private static var bannedAddressA:Array = [];
// This member’s identifier. Each group member will have
// an almost unique id
internal static var id:int = Math.floor(1000000000000*Math.random());
// index of tentative or actual pair
private static var index:int;
// This user’s name
internal static var nameS:String = “”;
// Paired (actual or tentative) member’s name.
internal static var foeNameS:String = “”;
// Used to add addresses to groupAddressA. Called from P2P
// when new connection is received.
internal static function addToGroup(s:String):void{
// Add s to groupAddressA if it is not in
// bannedAddressA
if(bannedAddressA.indexOf(s)==−1){
Peer-to-Peer Games
groupAddressA.push(s);
if(!connectionAddress){
index = 0;
connectionAddress = groupAddressA[0];
}
}
}
// Remove an address. Called from P2P when a connection
// is lost.
internal static function removeFromGroup(s:String):void{
var i:int = groupAddressA.indexOf(s);
groupAddressA.slice(i,1);
if(groupAddressA.length==0){
connectionAddress = “”;
}
}
// Find another tentative connectionAddress, if possible.
internal static function connectionAddressUpdateF():void{
if(groupAddressA.length>1){
index = (index+1)%groupAddressA.length;
connectionAddress = groupAddressA[index];
} else if(groupAddressA.length==1){
index = 0;
connectionAddress = groupAddressA[0];
} else {
index = 0;
connectionAddress = “”;
}
foeNameS = “”;
connectionID = 0;
}
// getters, setters
internal static function get connectionAddressF():String{
return connectionAddress
}
internal static function set connectionAddressF(s:String):void{
if(bannedAddressA.indexOf(s)==−1){
connectionAddress = s;
index = groupAddressA.indexOf(s);
}
}
internal static function get connectionIDF():int{
return connectionID;
}
573
574
Chapter 12 n Social Gaming: Multiplayer Games
internal static function set connectionIDF(n:int):void{
connectionID = n;
}
// Banned addresses. These are addresses from which
// this user disconnected.
internal static function set bannedAddressF(s:String):void{
// Add s to bannedAddressA
bannedAddressA.push(s);
// Remove s from groupAddressA
for(var i:int=groupAddressA.length-1;i>=0;i– –){
if(groupAddressA[i]==s){
groupAddressA.splice(i,1);
break;
}
}
}
}
}
Appendix a
Errors That Trigger an
Error Message
Use this appendix to look up error messages that you encounter. This appendix contains information that supplements the Adobe Help file appendices.
1009: Cannot Access a Property or Method of a Null Object Reference
This is the most common error posted on the Adobe Flash-related forums. Fortunately, most of these errors are quick and easy to fix (assuming you read the section
on permit debugging). If there is only one object reference in the problematic line of
code, you can conclude that object does not exist when that line of code executes. For
example, if there is no object mc on-stage,
var mc:MovieClip;
mc.x = 0;
is the simplest code that would trigger a 1009 error. The variable mc has been
declared but is null because no MovieClip exists, and it has not been created. Trying
to access the x property (or any other property of mc) will result in a 1009 error
because you cannot access a property of a null (or nonexistent) object.
To remedy this problem, you must create the referenced object either on-stage or
with ActionScript. To create the object with code, use the new constructor:
var mc:MovieClip = new MovieClip();
mc.x = 0;
575
576
Appendix A n Errors That Trigger an Error Message
Precisely the same error will occur in many different guises. Here’s the same error
trying to reference a null object method:
var s:Sound;
s.play();
If there’s more than one object in the problematic line of code—for example:
mc1.x = mc2.width+mc2.x;
use the
trace()
function to find which objects are null:
trace(mc1);
trace(mc2);
mc1.x = mc2.width+mc2.x;
Of course, typos are every coder’s burden and are a common cause of 1009 errors.
Instead of comparing two names and trying to confirm that they are the same (for
example, an instance name in the Properties panel and an ActionScript name), it’s
more reliable to copy the name from one location and paste it to the other. Then
you can be certain the names are the same.
There are, however, more obtuse ways to trigger a 1009 error when you create and
delete objects on-stage and especially if you use timeline tweening. I have seen some
1009 errors that are impossible to diagnose without checking the History panel. And
if that panel is cleared or the problem was created far enough in the past that the
steps causing the error are no longer present in the History panel, there’s no way
(known to me) to deduce what caused the error. However, you can still fix those
errors.
If you’re certain you have an on-stage object whose instance name (in the Properties
panel) matches your ActionScript reference and it is in a frame that plays no later
than your code referencing that object, you may need to take a few steps backward
to solve the problem. But first make sure that frame plays before your object
reference.
You can always confirm that by using the trace() function: Place a trace(“frame“) in
the keyframe that contains your object. (If you have a document class, it will need to
extend the MovieClip class to do this.) And place a trace(“code“) just above the problematic line of code and test.
If you see “code” and then your error message, your code is executing before your
frame plays. Fix your code so it doesn’t execute until the object exists. I cannot tell
you how to do that because the solution will vary depending on your project.
Errors That Trigger an Error Message
If you see “frame” and then “code”, you have confirmed your object exists before your
code tries to reference it, so something else is causing the problem. This always boils
down to Flash being confused because you have or had more than one object with
that reference name.
To solve this problem, move the on-stage object to its own layer, clear all keyframes
in that layer except one, copy its reference from your ActionScript, and paste it into
the Properties panel. Use Movie Explorer to confirm there is no ActionScript creating
a duplicate reference and there is no other on-stage object with the same reference.
Then test.
There will be no 1009 referencing that object if you follow those steps and the
ActionScript referencing the object executes no sooner than the keyframe containing
the object. Now, add other needed keyframes and change the object properties as
needed.
Another way this error can occur is when trying to reference a loader’s content property before loading is complete:
var loader:Loader = new Loader();
loader.load(new URLRequest(“test.swf“)); // make sure you have a test.swf in the
correct directory
trace(MovieClip(loader.content).totalFrames); // will trigger a 1009 error
Because a loader’s content property is null until loading is complete, use an
Event.COMPLETE listener and listener function before referencing the loader’s content.
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,completeF);
loader.load(new URLRequest(“test.swf“)); // make sure you have a test.swf in the
correct directory
function completeF(e:Event):void{
trace(MovieClip(loader.content).totalFrames); // no error
}
Also, a DisplayObject’s root and stage properties are null until the object is added to
the display list. This error frequently occurs in a DisplayObject’s class file, where the
object is created using the new constructor, the object’s class constructor executes, and
a stage reference is made before the object is added to the display list. For example:
var custom_mc:Custom_MC = new Custom_MC();
addChild(custom_mc);
will trigger this error if
Custom_MC
looks something like:
package {
import flash.display.MovieClip;
577
578
Appendix A n Errors That Trigger an Error Message
public class Custom_MC extends MovieClip {
public function Custom_MC() {
// stage is null at this point, triggering a 1009 error
this.x=stage.stageWidth/2;
}
}
}
To test if the problem is whether an object has been added to the display list, you can
use:
trace(this.stage);
added above the problematic line of code. The trace output will be null. For example:
package {
import flash.display.MovieClip;
public class Custom_MC extends MovieClip {
public function Custom_MC() {
trace(this.stage);
this.x=stage.stageWidth/2;
}
}
}
To remedy this problem, use the
ence the stage:
Event.ADDED_TO_STAGE
listener before trying to refer-
package {
import flash.display.MovieClip;
public class Custom_MC extends MovieClip {
public function Custom_MC() {
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
this.x=stage.stageWidth/2; // stage is defined when this executes
}
}
}
1013: The Private Attribute May Be Used Only on Class Property Definitions
This is commonly caused by mismatched curly brackets {} in a class file in a line of
code above the line that is mentioned in the error message.
1046: Type Was Not Found or Was Not a Compile-Time Constant: xxxx
You forgot to save the xxx class file, you have a typo, or you need to import the
needed class, xxxx.
Errors That Trigger an Error Message
To remedy the later, open the Flash help files > Classes > xxxx. At the top you will
see a package listed (for example, fl.controls) that you will use in the needed import
statement. Add the following to your scope:
import fl.controls.xxxx
(And in this example, the needed class type is a component, so you’ll need that component in your FLA’s library, too.)
1061: Call to a Possibly Undefined Method xxxx through a Reference with Static
Type flash.display:DisplayObject
You are trying to reference a method defined on a MovieClip timeline using dot notation, but the Flash compiler doesn’t recognize that MovieClip as a MovieClip. To remedy this, explicitly cast the MovieClip.
For example, you have a function/method
which you are trying to reference using:
xxxx()
defined on your root timeline,
root.xxxx();
To remedy, cast
root
as a
MovieClip:
MovieClip(root).xxxx();
1067: Implicit Coercion of a Value of Type xxxx to an Unrelated Type yyyy
You are trying to assign an object from class xxxx a value from class yyyy. The most
common error of this type seen on the Adobe forums is when trying to assign text to
a TextField and the code forgets to use the TextField’s text property:
var tf:TextField = new TextField();
tf = “This is test text.”; // should be using tf.text = “This is test text.”;
1078: Label Must Be a Simple Identifier
I get this error frequently because I’m speed typing, and instead of a semicolon, I
have the Shift key pressed and add a colon at the end of a line of code. Check the
line of code mentioned in the error message and look for a misplaced colon (typically
the last character in the line).
1118: Implicit Coercion of a Value with Static Type xxxx to a Possibly Unrelated
Type yyyy
Often loaded content needs to be cast. For example, if you load a SWF and try to
check a MovieClip property (for example, totalFrames), you will need to cast it as a
MovieClip:
var loader:Loader = new Loader();
579
580
Appendix A n Errors That Trigger an Error Message
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,completeF);
loader.load(new URLRequest(“someswf.swf“));
function completeF(e:Event):void{
trace(e.target.loader.content.totalFrames): // error expected
trace(MovieClip(e.target.loader.content).totalFrames)); // no error expected
}
Or, if you are loading xml:
var xml:XML;
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE,completeF);
loader.load(new URLRequest(“somexml.xml“));
function completeF(e:Event):void{
xml = e.target.data // error expected
xml = XML(e.target.data); // no error expected
}
When you use getChildByName, the compiler only knows the object is a DisplayObject.
If you want to use a property that is not inherited by DisplayObjects (for example,
mouseEnabled), you need to cast the object:
for (var i:int=0;i<4;i++) {
var opBtn:Btn_operator = new Btn_operator(); // where the Btn_operator class
// extends an InteractiveObject class
opBtn.name=i.toString();
addChild(opBtn);
}
// then you can use:
for (i=0;i<4;i++) {
opBtn = Btn_operator(getChildByName(i.toString()));
opBtn.mouseEnabled = false;
}
1119: Access of Possibly Undefined Property xxx through a Reference with Static
Type yyyy
You’re trying to reference a property that doesn’t exist for that class type. There are
three possiblities:
n
You have a typo and should correct your typing to eliminate the error.
n
You are trying to reference a property that does not exist for that object’s data
type, and you should check the ActionScript help files to remedy.
n
You are trying to reference a property that does exist for that object’s data type,
but the compiler is confused and is displaying a misleading error message. In
that situation, explicitly cast the data type of the object.
Errors That Trigger an Error Message
For example, if you have a
timeline:
DisplayObject
(for example,
dobj)
added to the
root
root.dobj; // may trigger a 1119 error
MovieClip(root).dobj; // will not trigger a 1119 error
In fact, when you use root in ActionScript 3, you almost always will need to case it as
a MovieClip unless you have a document class that extends the Sprite class. In that
situation, you will need to cast root as a Sprite:
MovieClip(root); // or
Sprite(root):
Also, if
this.parent
is a
MovieClip:
this.parent.ivar; // may trigger a 1119 error
MovieClip(this.parent).ivar; // will not trigger a 1119
If you’re using one of your own class objects:
var opBtnParent:Sprite = new Sprite();
addChild(opBtnParent);
for (var i:int=0;i<4;i++) {
var opBtn:Btn_operator = new Btn_operator();
opBtnParent.addChild(opBtn);
}
// then you can use:
for (i=0;i<opBtnParent.numChildren;i++) {
Btn_operator(opBtnParent.getChildAt(i)).mouseEnabled = false;
}
Or, you may be trying to reference an object created outside the class. In that situation, make sure your class is dynamic, for example:
dynamic public class YourClass extends MovieClip{
You can now add properties to
YourClass
instances from outside
YourClass.
1120: Access of Undefined Property Error xxxx
Either you have a typo, you have forgotten that ActionScript is case sensitive, or you
have defined the variable somewhere but your reference is out of scope of the defined
variable. Typically, the latter occurs when you make a variable local to a function and
then try to reference the variable outside that function.
For example:
function f():void{
var thisVar:String = “hi“;
}
trace(thisVar);
581
582
Appendix A n Errors That Trigger an Error Message
will trigger an 1120 error because thisVar is local to f() (as a consequence of prefixing its definition with the keyword var inside a function body).
To remedy, use the following (but you will need to call
f()
before
thisVar
has value
“hi“):
var thisVar:String;
function f():void{
thisVar = “hi“;
}
trace(thisVar);
Check the “Function Scope” section in Chapter 2 for more information.
1120: Access of Undefined Property Mouse
Import the needed class
flash.ui.Mouse:
import flash.ui.Mouse;
1151: A Conflict Exists with Definition xxxx in Namespace Internal
You have more than one of the following statements in the same scope:
var xxxx:SomeClass;
// and/or
var xxxx:SomeClass = new SomeClass();
To remedy, change all but the first to:
xxxx = new SomeClass();
1152: A Conflict Exists with Inherited Definition xxxx in Namespace Public
You’re trying to use an ActionScript keyword as a variable. To remedy this, change
that variable’s name to a non-keyword.
1178: Attempted Access of Inaccessible Property xxxx through a Reference with
Static Type YYYY
Variable xxxx typed private in class YYYY when should be protected, internal or public.
1180: Call to a Possibly Undefined Method addFrameScript
You have code (or even an empty space) on a timeline, and your document class is
extending the Sprite class. To remedy this, extend MovieClip or remove code from all
timelines. To find timeline code, use Movie Explorer and toggle only the Show
ActionScript button.
1180: Call to a Possibly Undefined Method xxxx
If you think you have defined that method, check for a typo. In other words, check
your method’s spelling and use copy and paste to eliminate typo issues.
Errors That Trigger an Error Message
Or, you have a path problem. To remedy this, check your path and check the relevant (MovieClip or class) scope section in Chapter 2 to resolve path problems.
Or, if xxxx is a class name, you failed to define/save that class in the correct directory.
To remedy this, save the needed class file and make sure the method xxxx is defined
in your class.
Or, you are trying to use a method defined in a class that has not been imported. To
remedy this, import the needed class.
Or, you are trying to use a method xxxx that is not inherited by your class. For example, trying to use addEventListener in a custom class that does not extend a class with
the addEventListener method will trigger a 1180 error. To remedy this, extend a class
that has this method.
Or, you nested a named function. To remedy, un-nest it. In ActionScript, you can use
named and anonymous functions. A named function has the form:
function f1():void{
}
An anonymous function has the form:
var f1:Function = function():void{
}
In the following code, both
f1().
f1()
and
f2()
are named functions, and
f2()
is nested in
function f1():void{
trace(“f1“);
function f2():void{
trace(“f2“);
}
}
If you use this code, at no time will f2() be defined outside of f1(). If you try to call
f2() from outside of f1(), no matter how many times you call f1(), you will generate
an 1180 error: Call to a Possibly Undefined Method.
To remedy this, un-nest:
function f1():void{
}
function f2():void{
}
583
584
Appendix A n Errors That Trigger an Error Message
Alternatively, nest an anonymous function in a named function:
var f2:Function;
function f1():void {
f2 = function():void{
};
}
Or, nest two anonymous functions:
var f2:Function;
var f1:Function = function():void {
f2 = function():void{
};
}
Note that f2 is declared outside of
outside the f1 function body.
f1.
That is required if you want to call
f2
from
If you tried:
function f1():void {
var f2:Function = function():void{
trace(“f2“);
};
f2(); // this will work
}
But, if you try:
function f1():void {
var f2:Function = function():void{
trace(“f2“);
};
}
f1();
f2();
You will trigger an 1180: Call to a Possibly Undefined Method f2 error.
You can expect the same error if you nest a named function in an anonymous
function.
You can nest name and anonymous functions if you only call the nested function
from within the nesting function body. For example, the following two snippets will
not trigger an error.
Snippet 1:
function f1():void{
trace(1);
Errors That Trigger an Error Message
f2();
function f2():void{
trace(2);
}
f2();
}
f1();
Snippet 2:
var f1:Function=function():void{
trace(1);
f2();
function f2():void{
trace(2);
}
f2();
}
f1();
Nevertheless, although you can nest a named function in this situation without triggering an error, you should not. There is no benefit to nesting a named function, and
in addition to triggering 1180 errors, it makes your code less readable.
1203: No Default Constructor Found in Base Class xxx
Failure to call the superclass (or no constructor). To remedy this, either create a constructor or use super() in your already existing constructor.
SecurityError: Error #2000: No Active Security Context
The file you are trying to load does not exist. You probably have a typo or a path
problem.
If your file loads when tested locally but triggers that error when test online, look for
case mismatches. For example, trying to load file.SWF will load file.swf locally but
not online.
TypeError: Error #2007: Parameter Text Must Be Non-Null
You are trying to assign undefined text to a
the following will trigger this error:
var tf:TextField = new TextField();
var a:Array = [1,2];
var s:String;
tf.text = s;
// and
tf.text = a[2];
// will both trigger this error
TextField’s text
property. For example,
585
586
Appendix A n Errors That Trigger an Error Message
Error #2044: Unhandled IOErrorEvent:. text=Error #2035: URL Not Found
You have an incorrect path and/or file name. First, double-check your spelling and
your paths. Then make sure your load target’s path is relative to the location of
your SWF’s embedding HTML file’s location, if you are testing by opening the
HTML in a browser. If you are testing in the Flash IDE or testing by opening the
SWF directly, the path should be relative to the SWF.
Error #2047: Security Sandbox Error
Error #2048: Security Sandbox Error
Error #2049: Security Sandbox Error
You are probably trying to test a SWF file on your local computer (which is in your
local security sandbox) and that SWF is trying to connect to the Internet across that
security sandbox. To remedy, either upload your test files to a server on the Internet
or adjust your security settings at www.macromedia.com/support/documentation/en/
flashplayer/help/settings_manager04.html.
2136: The SWF file file:///somepath/someswf.swf Contains Invalid Data
You are trying to use the “new” constructor with a document class. To remedy this,
either remove that class from your document class or remove the “new” constructor
applied to that class.
5000: The Class ‘xxx’ Must Subclass ‘yyy’ Since It Is Linked to a Library Symbol of
That Type
Can occur because a previous error stoned the compiler, triggering errors that do not
exist. But if it’s the first (or only) error listed, your ‘xxx’ class must usually extend a
MovieClip or SimpleButton. Right-click your library symbol, click Properties, and
check the base class listed there.
5008: The Name of Definition ‘Xxxx’ Does Not Reflect the Location of This File.
Please Change the Definition’s Name inside This File, or Rename the File.
The file name and the class name must match. For example:
package {
public class Xxxx {
public function Xxxx(){
}
}
}
must be saved as Xxxx.as (and case matters).
Appendix B
Errors That Do Not Trigger
Error Messages
Use this appendix to find and correct coding errors that do not trigger a compiler
error message.
Code That Doesn’t Work
This is a common lament. Adobe forum posters frequently state that they see no error
message, but their code doesn’t work. Finding the problem and then the solution
always involves the same steps: Use the trace() function and then use it again and
again and again until you pinpoint the problem. You can always pinpoint the problem using the trace() function.
An example of code that doesn’t work would be code for a button that, when clicked,
should load a SWF, but when you click the button, the SWF doesn’t load.
That would typically be posted as, Why doesn’t my SWF load? But anyone who has
been answering forum posts for more than a few months knows it is not a good idea
to assume the poster is correct. That SWF may be loading and the poster doesn’t realize it.
The only thing that can be safely assumed is that the poster clicked something and
failed to see what he expected to see (the loaded SWF). That might mean the SWF
didn’t load, but it also might mean the button that is supposed to start the SWF loading wasn’t clicked, or the button was clicked and the SWF did load but isn’t added to
the display list. Or, it loaded and is added to the display list, but it isn’t visible because
something is covering it. Or, the SWF is positioned off-stage, or its alpha is 0, or its
587
588
Appendix B n Errors That Do Not Trigger Error Messages
visible property is false. Or, maybe the SWF was loaded and added to the display list
and would’ve been visible, but something occurred before the display was rendered to
make the loaded SWF not visible.
There are almost always a lot of reasons why code can seem not to work. To find the
problem, you should start by pinpointing exactly where your code fails. In other words,
use the trace() function.
So, let’s debug this code, which should load test.swf and display it when btn is clicked:
btn.addEventListener(MouseEvent.CLICK,btnF);
function btnF(e:MouseEvent):void {
var loader:Loader = new Loader();
loader.load(new URLRequest(“test.swf“));
addChild(loader);
}
If you don’t see what you expect, first confirm that what you expect to see is reasonable by clicking test.swf or its embedding HTML to confirm that you can see something on-stage and thereby judge whether test.swf is displayed. Or, use the trace()
function in test.swf to judge whether it is loaded.
Having done that, you should make sure that code executes. Depending on where that
code is located, it might not execute. To check whether that code is executing, you
could use:
trace(“OK“);
btn.addEventListener(MouseEvent.CLICK,btnF);
function btnF(e:MouseEvent):void {
var loader:Loader = new Loader();
loader.load(new URLRequest(“test.swf“));
}
When you test that code, you should see OK in the output panel (or that code is not
executing or you disabled trace output). If that code is not executing (in other words,
you do not see OK in the output panel), you need to fix that:
1. If that code is timeline code, you can conclude that it is attached to a frame that
hasn’t played at the time you clicked btn.
2. If it’s in a class, that class has not been instantiated when btn is clicked.
3. And/or that listener is added within a function that needs to be called before btn
is clicked.
Code That Doesn’t Work
Assuming you see OK in the output panel, the next step would be to confirm your btn
listener function (btnF) is being called:
btn.addEventListener(MouseEvent.CLICK,btnF);
function btnF(e:MouseEvent):void {
trace(e);
var loader:Loader = new Loader();
loader.load(new URLRequest(“test.swf“));
addChild(loader);
}
After clicking btn, you should see a MouseEvent.CLICK event in your output panel
([MouseEvent type=“click” etc]). If you do not, btn is not being clicked. You might
think you’re clicking btn, but Flash does not.
There are quite a few ways that can happen. Because there is no error message, you
know Flash is finding something with reference btn. So, btn exists. It is just not the
object you’re clicking.
You should check for all btn objects by using Movie Explorer. If you find more than
one, that may be the problem. Create a test FLA and confirm that multiple btn objects
are the problem by removing all but the one closest to Frame 1 of the parent timeline
and retesting your code. If that works, re-add the other keyframes that contain btn
(without dragging anything from the library), and the error shouldn’t return.
If you do see [MouseEvent type=“click” etc] in the output panel and there are no error
messages, you know test.swf exists in a valid path (otherwise, Flash would generate an
Error #2044: Unhandled IOErrorEvent:. text=Error #2035: URL Not Found message).
But, you can add an Event.COMPLETE listener with a trace() function to confirm that
test.swf is loading.
If none of the above reveals the problem, then you know test.swf is loading.
To find out why it’s not being displayed, you will need to execute code some milliseconds after loading is complete to see whether loader is still on-stage and visible or
whether some other code is moving, removing, or otherwise changing loader so its
content is no longer visible.
The following code should do that, allowing you to check whether loader is being used
to load something else or its alpha or x or visible or some other property is causing the
problem.
btn.addEventListener(MouseEvent.CLICK,btnF);
var loader:Loader = new Loader();
var t:Timer = new Timer(100,1);
589
590
Appendix B n Errors That Do Not Trigger Error Messages
t.addEventListener(TimerEvent.TIMER,traceF);
function btnF(e:MouseEvent):void {
loader.load(new URLRequest(“test.swf“));
addChild(loader);
t.start()
}
function traceF(e:TimerEvent):void{
// The url property should show test.swf confirming loader is not
// being used to load something other than test.swf.
// The numChildren property should be one greater than loader’s
// childIndex or something is being positioned over loader.
trace(loader.content.loaderInfo.url,loader.parent.numChildren,loader.parent.get
ChildIndex(loader))
var xml:XML = describeType(loader);
var xmlList:XMLList = xml.accessor;
for(var i:int=0;i<xmlList.length();i++){
if(xmlList[i].@access.indexOf("read")>-1){
// This will reveal most of the properties of loader
// and their values (like alpha, x, y, visible, etc).
trace(xmlList[i].@name,loader[xmlList[i].@name])
}
}
}
You will need to check those properties and values carefully. While it would obviously
be a problem if a property like visible is false, there are less obvious ways a property
change can cause a problem. For example, if you are loading a SWF that contains only
non-embedded classic text, and you rotate your loader or its content, you won’t see
anything on-stage from the loaded SWF until you embed the text’s font.
Errors Caused by Asynchronous Code Execution
Most code in Flash executes in an orderly, easy-to-follow way. If you have:
f1(); // where functions f1() and f2() are defined in this scope
f2();
you can be sure the code in f1() will execute before the code in f2(). That is an example of synchronous code execution: If statement1 precedes statement2, statement1
executes before statement2. Most ActionScript executes synchronously.
gotoAndPlay and gotoAndStop (to a Frame Not Yet Loaded)
But some code executes asynchronously. In particular, file loading is asynchronous,
and load-complete listener functions execute asynchronously. For example:
var urlLoader:URLLoader = new URLLoader();
urlLoader.load(new URLRequest(“test.txt“)); // test.txt should be in the same file
with your flash files
trace(urlLoader.data); // undefined because loading is not complete
and
var dataS:String;
var urlLoader:URLLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE,completeF);
urlLoader.load(new URLRequest(“test.txt“));
function completeF(e:Event):void{
dataS = urlLoader.data;
}
trace(urlLoader.data); // undefined because this executes before completeF() executes
trace(dataS); // null because dataS is typed but no value is assigned until completeF()
executes
To remedy, you should use listener functions to trigger code that depends on loaded
assets:
var dataS:String;
var urlLoader:URLLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE,completeF);
urlLoader.load(new URLRequest(“test.txt“));
function completeF(e:Event):void{
dataS = urlLoader.data;
dependentCodeF();
}
function dependentCodeF():void{
trace(dataS);
// etc
}
gotoAndPlay and gotoAndStop (to a Frame Not
Yet Loaded)
If you use either of these and find that Flash isn’t going to the correct frame, after
checking for typos you should ensure that the target frame is loaded. This is more
likely to occur when you’re testing online, but I’ve seen the same issue when testing
locally, too.
591
592
Appendix B n Errors That Do Not Trigger Error Messages
To remedy this, don’t execute the goto until the needed frame is loaded. It’s easiest to
use a complete listener to ensure that all frames are loaded if you encounter this
problem:
goto is applied to the SWF that contains your code
this.loaderInfo.addEventListener(Event.COMPLETE,completeF);
function completeF(e:Event):void{
this.gotoAndStop(this.totalFrames);
}
Example 1:
goto is applied to a loaded swf
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,completeF);
loader.load(new URLRequest(“test.swf“));
function completeF(e:Event):void{
var mc:MovieClip = MovieClip(loader.content); // the loader’s content
property, after loading is complete, is the loaded swf’s main timeline (which is a
MovieClip). Unfortunately, the Flash compiler (in strict mode) often loses track of
what object is of what class type and triggers a
mc.gotoAndStop(mc.totalFrames);
}
Example 2: The
Lost Object References
There are many ways this occurs, but most commonly it occurs in for loops and while
loops. One reference is used repeatedly, so with each loop iteration the current object
reference overwrites the previous object reference. At the end of the for loop, that
reference refers to only the last created object. Here’s the typical problem (already
mentioned in Chapter 1):
for (var io:int = 0; io < 4; io++) {
var opBtn:Btn_operator = new Btn_operator();
opBtn.addEventListener(MouseEvent.CLICK, pressOperator);
}
And then later in the code:
opBtn.mouseEnabled = false; // only one opBtn instance will be disabled
Among the many ways to resolve this issue, you can:
1. Assign a name property to each object and use the
to the object’s parent:
getChildByName()
for (var i:int=0;i<4;i++) {
var opBtn:Btn_operator = new Btn_operator();
method applied
Lost Object References
opBtn.name=i.toString();
addChild(opBtn);
}
// then you can use:
for (i=0;i<4;i++) {
var btn = getChildByName(i.toString()); // if you use an opBtn reference, use
Appendix A to debug the Flash error
btn.mouseEnabled = false;
}
2. Push each object into an array and later loop through the array:
var opBtnA:Array = [];
for (var i:int=0;i<4;i++) {
var opBtn:Btn_operator = new Btn_operator();
opBtnA.push(opBtn);
addChild(opBtn);
}
// then you can use:
for (i=0;i<opBtnA.length;i++) {
opBtnA[i].mouseEnabled = false;
}
3. Add each object to a parent that contains nothing but those objects and loop
through the parent’s children:
var opBtnParent:Sprite = new Sprite();
addChild(opBtnParent);
for (var i:int=0;i<4;i++) {
var opBtn:Btn_operator = new Btn_operator();
opBtnParent.addChild(opBtn);
}
// then you can use:
for (i=0;i<opBtnParent.numChildren;i++) {
Btn_operator(opBtnParent.getChildAt(i)).mouseEnabled = false;
}
4. Use a different object reference in each loop:
for (var i:int=0;i<4;i++) {
this["opBtn"+i] = new Btn_operator();
}
// then you can use:
for (i=0;i<4;i++) {
this["opBtn"+i].mouseEnabled = false;
}
593
594
Appendix B n Errors That Do Not Trigger Error Messages
Repeatedly Executed Code
This only happens when code is added to a timeline frame and that frame repeatedly
plays. For example, if the following code is attached to a frame that repeatedly executes, you will find f() being executed more and more frequently.
var t:Timer = new Timer(1000,0);
t.addEventListener(TimerEvent.TIMER,f);
t.start();
function f(e:TimerEvent):void{
trace(getTimer());
}
To remedy, use a Boolean so that code executes only the first time the frame plays:
var alreadyExecuted:Boolean;
if (! alreadyExecuted) {
alreadyExecuted=true;
var t:Timer=new Timer(1000,0);
t.addEventListener(TimerEvent.TIMER,f);
t.start();
function f(e:TimerEvent):void {
trace(getTimer());
}
}
The above code is also an example of a runaway loop defined by an accelerating loop.
If you use setInterval(), you can prevent it from triggering runaway loops by clearing the interval prior to each setting:
var testI:int;
clearInterval(testI);
testI = setInterval(testF,1000);
function testF(){
trace(getTimer());
}
or enclose in a Boolean. Or, even better, do both:
var alreadyExecuted:Boolean;
if (! alreadyExecuted) {
var testI:int;
clearInterval(testI);
testI = setInterval(testF,1000);
function testF(){
trace(getTimer());
}
}
Problems Related to Class File Use
Problems Related to Class File Use
1. To see the results of a changed class file, you must save your changes and then
test or publish a new SWF.
2. If, in your default directory, you have a class file C1.as:
package {
public class C1 {
public function C1() {
trace(“C1“);
}
}
}
and in a subdirectory Sub of your default directory, you have a different class file
C1.as:
package Sub{
public class C1 {
public function C1() {
trace(“C.C1“);
}
}
}
then in any other class instantiating a C1 instance, even if Sub.C1 is imported into
that class and no matter the location of the other class, the C1 class from the
default directory will be used, not the C1 class from the Sub directory.
595
This page intentionally left blank
INDEX
#2000 No Active Security Context
error message, 585
#2007 Parameter Text Must
Be Non-Null error
message, 585
#2035 URL Not Found error
message, 586
#2044 Unhandled IOErrorEvent.
text=Error #2035 URL
Not Found error
message, 586
#2047 Security Sandbox Error error
message, 586
#2048 Security Sandbox Error error
message, 586
#2049 Security Sandbox Error error
message, 586
% (modulo operator), 67–68, 214
+1 button (Google+), 509
3D games (Flare3D)
3D particle effects, 421–435
astro character, 393–401
cameras, 393–401
cost, 384
Data class, 392
downloading, 384
energy 3D model, 393–401
example directory, 421–435
fans, 393–401
files, 384–386
first-person view, 421–435
GameOverView class, 391–392
GameView class, 389–391
initializing world, 387–392
IntroView class, 388–389
libraries, 384–386
Main class, 387–388
mines, 393–401
objects
animating, 393–401
collision detection, 401–410
shadows, 411–420
opening, 386
overview, 383–384
ParticleEmitter3D class, 421–435
preloader display, 393–401
score, 411–420
sound, 411–420
terminology, 384
3D particle effects (Flare3D),
421–435
1009 Cannot Access a Property or
Method of a Null Object
Reference error message,
575–578
1013 The Private Attribute May Be
Used Only on Class
Property Definitions
error message, 578
1046 Type Was Not Found or Was
Not a Compile-Time
Constant error message,
578–579
1061 Call to a Possibly Undefined
Method xxxx through a
Reference with Static
Type flash.display, 579
1067 Implicit Coercion of a Value
of Type xxxx to an
Unrelated Type yyyy
error message, 579
1078 Label Must Be a Simple
Identifier error
message, 579
1118 Implicit Coercion of a Value
with Static Type xxxx to a
Possibly Unrelated Type
yyyy error message,
579–580
1119 Access of Possibly Undefined
Property xxxx through a
Reference with Static
Type yyyy error message,
580–581
1120 Access of Undefined Property
Error xxxx error message,
581–582
1120 Access of Undefined Property
Mouse error message, 582
1151 A Conflict Exists with
Definition xxxx in
Namespace Internal error
message, 582
1152 A Conflict Exists with
Definition xxxx in
Namespace Public error
message, 582
1178 Attempted Access of
Inaccessible Property
xxxx through a Reference
with Static Type yyyy, 582
1180 Call to a Possibly Undefined
Method addFrameScript
error message, 582
597
598
Index
1180 Call to a Possibly Undefined
Method xxxx error
message, 582–585
1203 No Default Constructor
Found in Base Class xxx
error message, 585
2000 No Active Security Context
error message, 585
2007 Parameter Text Must Be
Non-Null error
message, 585
2035 URL Not Found error
message, 586
2044 Unhandled IOErrorEvent.
text=Error #2035 URL
Not Found error
message, 586
2047 Security Sandbox Error error
message, 586
2048 Security Sandbox Error error
message, 586
2049 Security Sandbox Error error
message, 586
2136 The SWF file file…someswf.
swf Contains Invalid Data
error message, 586
5000 The Class xxx Must Subclass
yyy Since It Is Linked to a
Library Symbol of That
Type error message, 586
5008 The Name of Definition Xxxx
Does Not Reflect the
Location of This File.
Please Change the
Definition Name inside
This File, or Rename the
File. error message, 586
10,000 MovieClips test
Starling, 242–247
A
A Conflict Exists with Definition
xxxx in Namespace
Internal error
message, 582
A Conflict Exists with Definition
xxxx in Namespace Public
error message, 582
Access of Possibly Undefined
Property xxxx through a
Reference with Static
Type yyyy error message,
580–581
Access of Undefined Property Error
xxxx error message,
581–582
Access of Undefined Property
Mouse error message, 582
ActionScript
ActionScript 3.0 API Language
Reference, 81–84
Facebook ActionScript API,
478–482
Google+ ActionScript 3.0 library,
533–552
Google+ API, 513
JavaScript communication,
56–57, 445–451, 478
Tweetr ActionScript library,
494–508
ActionScript 3.0 API Language
Reference, 81–84
ActionScript API (Facebook),
478–482
ADB (Android Debug Bridge),
371–372
addChild() method, 87
adjacent angles, 62
Adobe Monocle, 194
Adobe Scout, 194–195
ADT (Adobe AIR Developer Tool),
372–374
AIR Debug Launcher, testing
Android games, 368
Air for Android
Deployment tab, 377–380
General tab, 374–376
Icons tab, 380
Languages tab, 381–382
Permissions tab, 380–381
Air for iOS
Deployment tab, 327–330
General tab, 323–327
Icons tab, 330
Languages tab, 330–331
Android Debug Bridge (ADB),
371–372
Android games
Android Debug Bridge (ADB),
371–372
Controller class, 349–359
Data class, 361–362
distributing, 382
GameOverView class, 359–360
GameView class, 347–349
IntroView class, 342–346
Light_Pool class, 366–367
Main class, 339–342
overview, 337–339
publishing (Air for Android)
Deployment tab, 377–380
General tab, 374–376
Icons tab, 380
Languages tab, 381–382
Permissions tab, 380–381
Replay_Btn class, 363
Slider class, 363–365
Switch class, 362
Switch_Pool class, 366
testing
ADB (Android Debug
Bridge), 371–372
ADT (Adobe AIR Developer
Tool), 372–374
AIR Debug Launcher, 368
emulators, 368–371
overview, 367
TF class, 362–363
TF_Pool class, 367
virtual device emulator, 194
angles
adjacent angles, 62
angles of reflection, 62–64
Pythagorean Theorem, 64–65
straight angles, 61
sum of triangle’s angles, 61
vertical angles, 62
animating objects (Flare3D),
393–401
APIs
Flash, 81–84
Google+. See Google+
Stage3D (Flare3D)
3D particle effects, 421–435
animating objects, 393–401
astro character, 393–401
cameras, 393–401
collision detection, 401–410
cost, 384
Data class, 392
downloading, 384
energy 3D model, 393–401
example directory, 421–435
fans, 393–401
files, 384–386
Index
first-person view, 421–435
GameOverView class,
391–392
GameView class, 389–391
initializing world, 387–392
IntroView class, 388–389
libraries, 384–386
Main class, 387–388
mines, 393–401
opening, 386
overview, 383–384
ParticleEmitter3D class,
421–435
preloader display, 393–401
score, 411–420
shadows, 411–420
sound, 411–420
terminology, 384
appID (Facebook JavaScript API),
451–452
applications (Google+ API sample
Flash applications),
518–533
arithmetic (CPU/GPU usage),
212–218
arrays
associative arrays, 50
copying, 49
CPU/GPU usage, 256–258
creating, 47–50
Dictionary, 55
key-press combinations, 110–114
notation, 50–51
shuffling, 49–50
strings, 48–49
vectors, 78–79
assigning undeclared properties,
45–46
associative arrays, 50
astro character (Flare3D), 393–401
asynchronous errors, 6, 590–591
Attempted Access of Inaccessible
Property xxxx through a
Reference with Static
Type yyyy, 582
authentication (Twitter requests)
not required, 486–491
required, 492–508
authorizing requests (Google+
API), 517–533
auto-formatting, 16
B
badges (Google+), 509–510
BitmapData class, 51–54
bitmaps
BitmapData class, 51–54
CPU/GPU usage
cacheAsBitmap property,
219–220
cacheAsBitmapMatrix
property, 219–220
partial blitting, 228–231
stage blitting, 220–228
bitwise operators (CPU/GPU
usage), 212–218
blitting
CPU/GPU usage
partial blitting, 228–231
stage blitting, 220–228
Starling comparison,
236–256
boundary violations, 110–114,
149–172
brackets (errors), 6–7
break statements, 60
browsers, cookies, 76
buttons
Google+
+1, 509
code, 510–513
Share, 510
Twitter
Follow, 485
Hashtag, 485
Mention, 485–486
Share a Link, 484
C
cacheAsBitmap property (CPU/
GPU usage), 219–220
cacheAsBitmapMatrix property
(CPU/GPU usage),
219–220
calculating distance (objects),
64–65
Call to a Possibly Undefined
Method addFrameScript
error message, 582
Call to a Possibly Undefined
Method xxxx error
message, 582–585
Call to a Possibly Undefined
Method xxxx through a
Reference with Static
Type flash.display, 579
callback functions, managing
memory, 205–206
cameras (Flare3D), 393–401
Cannot Access a Property or
Method of a Null Object
Reference error message,
575–578
certificates (iOS games)
development certificates
(publishing), 312–323
distribution certificates, 313,
331–335
changeRegPt() function, 75–76
characters (Flare3D astro
character), 393–401
chunks (loops), 58–60
class coding, 17–18
classes
BitmapData, 51–54
class coding, 17–18
CollisionDetection, 52
CombatView
creating, 125–135
iOS games, 274–279
CombatView_Pool (iOS games),
279–280
constructors, 30, 36–37
Controller
Android games, 349–359
iOS games, 260, 280–296
Controller_Pool (iOS games), 297
creating, 27–31
Data
Android games, 361–362
Flare3D, 392
iOS games, 305–308
peer-to-peer multiplayer
games, 572–574
directories, 29, 31
dynamic, 45–46
encapsulation, 95–105
EndView, 95. See also
GameOverView class
EnemyTank, 96–105. See also
tanks
creating, 86, 125–135
iOS games, 301–302
599
600
Index
classes (Continued)
EnemyTank_Pool (iOS games), 302
events, dispatchers, 38–39,
56–58
extending, 30
ExternalInterface, 56–57,
445–451, 478
files
directories, 96–97
pseudo classes, 97
Flash API, 82–84
GameOverView, 95. See also
end view
Android games, 359–360
Flare3D, 391–392
iOS games, 302–304
GameOverView_Pool (iOS
games), 304–305
GameView class, 95–105
Android games, 347–349
Flare3D, 389–391
importing, 29, 31
IntroView class, 95–105, 172–192
Android games, 342–346
Flare3D, 388–389
iOS games, 262–271
IntroView_Pool (iOS games),
271–272
KBcontrols, 96–105
Keyboard, 88–89
KeyboardEvent, 89–91
KeyboardType, 88–89
Light_Pool (Android games),
366–367
Main
Android games, 339–342
creating, 81–82
Flare3D, 387–388
iOS games, 260–262
organizing, 95–105
peer-to-peer multiplayer
games, 554–569
Math (CPU/GPU usage),
215–218
methods. See also functions
addChild(), 87
FB (Facebook JavaScript API),
453
gotoAndPlay, 591–592
gotoAndStop, 591–592
hitTestObject(), 92–93
hitTestPoint(), 92–93
internal, 31–33, 42–43
private, 31–33, 42–43
protected, 31–33, 42–43
public, 31–33, 37, 42–43
static, 42–43
MovieClip, extending, 85–86
names, 30
NetGroup (peer-to-peer
multiplayer games),
569–572
P2P (peer-to-peer multiplayer
games), 569–572
packages, 29
ParticleEmitter3D, 421–435
PlayerTank class, 96–105. See also
tanks
creating, 85–86
iOS games, 299–300
PlayerTank_Pool (iOS games),
300–301
properties
getters, 34–39
internal, 31–33
private, 31–33
protected, 31–33
public, 31–33
setters, 34–39
static, 39–41
Replay_Btn (Android games),
363
saving (SWF), 87
scope, 23–24
singletons, 43–45
Slider
Android games, 363–365
iOS games, 272–274
Stage, 90
Switch (Android games), 362
Switch_Pool (Android games), 366
Tank (iOS games), 298–299
TF (Android games), 362–363
TF_Pool (Android games), 367
Timer (iOS games), 297
TimerExt (iOS games), 297
troubleshooting files, 595
Tween, 76–77
TweenLite, 76–77
Vector, 78–79
clearing memory (garbage
collection), 57
listeners, 57–58
overview, 57
preparing objects, 135–149
code
asynchronous errors, 6, 590–591
class coding, 17–18
conditional compiling, 54–55
Config Constants, 54–55
debugging errors that do not
trigger error messages,
587–590
formatting, 16
Google+ buttons, 510–513
timeline coding, 17–18
troubleshooting repeatedly
executed code, 594
collision detection
CollisionDetection class, 52
Flare3D, 401–410
iOS games, 260
moving objects, 149–172
objects, 51–54, 401–410
pixels, 51–54
CollisionDetection class, 52
color, 51–54
CombatView class
creating, 125–135
iOS games, 274–279
CombatView_Pool class (iOS
games), 279–280
communication, ActionScript/
JavaScript, 56–57,
445–451, 478
compile-time errors, 3–5
conditional compiling, 54–55
Config Constants, 54–55
configuring (Config Constants),
54–55
constants (Config Constants), 54–55
constructors (classes), 30, 36–37
Controller class
Android games, 349–359
iOS games, 260, 280–296
Controller_Pool class (iOS
games), 297
controllers. See Controller class
controlling parameters (users),
172–192
Index
cookies (browsers), 76
copying arrays, 49
cost (Flare3D), 384
CPU/GPU usage. See also
performance
arrays, 256–258
bitmaps
cacheAsBitmap property,
219–220
cacheAsBitmapMatrix
property, 219–220
partial blitting, 228–231
stage blitting, 220–228
listeners, 256
loops, 231–233
math
arithmetic, 212–218
bitwise operators, 212–218
division, 212–213
Math class, 215–218
modulo operator, 214
multiplication, 213–214
memory relationship, 195–197
mouse, 233–235
remote listeners, 235
Stage3D
overview, 235–236
Starling 10,000 MovieClips
test, 242–247
Starling Mudbubble Boy test,
250–255
Starling Patch the Scarecrow
test, 247–250
Starling/blitting comparison,
236–256
variables, 256
vectors, 256–258
creating
arrays, 47–50
classes, 27–31
CombatView class, 125–135
custom cursors, 114–118
EnemyTank class, 86, 125–135
game-over views, 149–172
games, 86
Main class, 81–82
objects, 125–135
PlayerTank class, 85–86
registration points, 85
rotating objects, 114–118
shooting functionality, 114–118
sound, 125–135, 149–172
statistics displays, 149–172
views, 125–135
cursors, creating, 114–118
custom profiles, debugging, 8–9
D
dashes (errors), 7
data, organizing, 172–192
Data class
Android games, 361–362
Flare3D, 392
iOS games, 305–308
peer-to-peer multiplayer games,
572–574
debugging. See also errors; testing
compile-time errors, 3–5
custom profiles, 8–9
errors that do not trigger error
messages, 587–590
formatting, 16
line numbers, 3
online, 2
permit debugging, 2–8
run-time errors, 3–8
scope, 17
templates, 18
textfields, 1–2
tools, 1–2
trace() function, 1–2, 11–14
delay (keyboard repeat delay),
104–110
Deployment tab
Air for Android, 377–380
Air for iOS, 327–330
detecting collisions. See collision
detection
development certificates
(publishing iOS games),
312–323
development provisioning profiles
(publishing iOS games),
312–323
development/target platform
comparison, 194–195
device emulator, 194
Dialogs (Facebook JavaScript API),
453–466
Dictionary, 55
directories
classes, 29, 31, 96–97
Flare3D example directory,
421–435
dispatchers (events), 38–39, 56–58
dispatchEvent (managing
memory), 205–206
displays. See views/displays
distance, calculating, 64–65
distributing
Android games, 382
iOS games, 313, 331–335
distribution certificates
(distributing iOS games),
313, 331–335
distribution provisioning profiles
(publishing iOS games),
313, 331–335
division (CPU/GPU usage),
212–213
do loops, 58–60
downloading Flare3D, 384
drawing tanks, 85
dynamic classes, 45–46
E
effects (Flare3D 3D particle
effects), 421–435
emulators
testing Android games, 368–371
virtual device emulator, 194
encapsulation (classes), 95–105
EndView class, 95. See also
GameOverView class
EnemyTank class, 96–105. See also
tanks
creating, 86, 125–135
iOS games, 301–302
EnemyTank_Pool class (iOS
games), 302
energy 3D model (Flare3D),
393–401
enterFrame loops, 60–61, 124–125
Error #2035 URL Not Found error
message, 586
Error #2044 Unhandled
IOErrorEvent. text=Error
#2035 URL Not Found
error message, 586
Error #2047 Security Sandbox
Error error message, 586
Error #2048 Security Sandbox
Error error message, 586
Error #2049 Security Sandbox
Error error message, 586
601
602
Index
errors. See also debugging; testing
asynchronous, 6, 590–591
brackets, 6–7
compile-time errors, 3–5
dashes, 7
number of, 16
run-time errors, 3–8
scope, 17
SWF names, 7–8
that do not trigger error
messages, 11–14
asynchronous code execution,
590–591
debugging code, 587–590
troubleshooting class files, 595
troubleshooting lost object
references, 592–593
troubleshooting repeatedly
executed code, 594
troubleshooting target frames,
591–592
that trigger error messages, 10
1009 Cannot Access a
Property or Method of a
Null Object Reference,
575–578
1013 The Private Attribute
May Be Used Only on
Class Property Definitions,
578
1046 Type Was Not Found or
Was Not a Compile-Time
Constant, 578–579
1061 Call to a Possibly
Undefined Method xxxx
through a Reference with
Static Type flash.display,
579
1067 Implicit Coercion of a
Value of Type xxxx to an
Unrelated Type yyyy, 579
1078 Label Must Be a Simple
Identifier, 579
1118 Implicit Coercion of a
Value with Static Type
xxxx to a Possibly
Unrelated Type yyyy,
579–580
1119 Access of Possibly
Undefined Property xxxx
through a Reference with
Static Type yyyy, 580–581
1120 Access of Undefined
Property Error xxxx,
581–582
1120 Access of Undefined
Property Mouse, 582
1151 A Conflict Exists with
Definition xxxx in
Namespace Internal, 582
1152 A Conflict Exists with
Definition xxxx in
Namespace Public, 582
1178 Attempted Access of
Inaccessible Property xxxx
through a Reference with
Static Type yyyy, 582
1180 Call to a Possibly
Undefined Method
addFrameScript, 582
1180 Call to a Possibly
Undefined Method xxxx,
582–585
1203 No Default Constructor
Found in Base Class xxx,
585
2136 The SWF file file…
someswf.swf Contains
Invalid Data, 586
5000 The Class xxx Must
Subclass yyy Since It Is
Linked to a Library
Symbol of That Type, 586
5008 The Name of Definition
Xxxx Does Not Reflect the
Location of This File.
Please Change the
Definition Name inside
This File, or Rename the
File., 586
Error #2044 Unhandled
IOErrorEvent. text=Error
#2035 URL Not Found, 586
Error #2047 Security Sandbox
Error, 586
Error #2048 Security Sandbox
Error, 586
Error #2049 Security Sandbox
Error, 586
SecurityError Error #2000 No
Active Security Context, 585
TypeError Error #2007
Parameter Text Must Be
Non-Null, 585
events
dispatchers, 38–39, 56–58
listeners
CPU/GPU usage, 235, 256
dispatchers, 38–39, 57–58
keyboards, 89–91, 104–105
managing memory, 211
remote, 235
mouse, 74
rollout (mouse movement),
118–124
touch events (iOS games), 259
example directory (Flare3D),
421–435
experimenting (testing), 14–16
extending
classes, 30
MovieClip class, 85–86
ExternalInterface class, 56–57,
445–451, 478
F
Facebook
ActionScript API, 478–482
JavaScript API
ActionScript communication,
446–451
appID, 451–452
Dialogs, 453–466
FB methods, 453
JavaScript SDK, 452
overview, 444–446
permissions, 453
social graph, 466–477
registering games, 438–444
fans (Flare3D), 393–401
FB methods (Facebook JavaScript
API), 453
files
class directories, 96–97
Flare3D, 384–386
pseudo classes, 97
troubleshooting classes, 595
filters (managing memory), 206
finding objects (stages), 87–88
first-person view (Flare3D),
421–435
Fisher-Yates shuffle, 49–50
Flare3D
3D particle effects, 421–435
astro character, 393–401
Index
cameras, 393–401
cost, 384
Data class, 392
downloading, 384
energy 3D model, 393–401
example directory, 421–435
fans, 393–401
files, 384–386
first-person view, 421–435
GameOverView class, 391–392
GameView class, 389–391
initializing world, 387–392
IntroView class, 388–389
libraries, 384–386
Main class, 387–388
mines, 393–401
objects
animating, 393–401
collision detection, 401–410
shadows, 411–420
opening, 386
overview, 383–384
ParticleEmitter3D class, 421–435
preloader display, 393–401
score, 411–420
sound, 411–420
terminology, 384
Flash API, 81–84
Flash applications (Google+ API),
518–533
focus (stage), 135–149
Follow button (Twitter), 485
for loops, 58–60
formatting code, 16
frameRate property, 74
frames, troubleshooting target
frames, 591–592
frameworks (Flare3D)
3D particle effects, 421–435
astro character, 393–401
cameras, 393–401
cost, 384
Data class, 392
downloading, 384
energy 3D model, 393–401
example directory, 421–435
fans, 393–401
files, 384–386
first-person view, 421–435
GameOverView class, 391–392
GameView class, 389–391
initializing world, 387–392
IntroView class, 388–389
libraries, 384–386
Main class, 387–388
mines, 393–401
objects
animating, 393–401
collision detection, 401–410
shadows, 411–420
opening, 386
overview, 383–384
ParticleEmitter3D class, 421–435
preloader display, 393–401
score, 411–420
sound, 411–420
terminology, 384
functions. See also methods
callback functions, 205–206
changeRegPt(), 75–76
getLocal(), 76
getTimer(), 57
random(), 68–69
scope, 24–26
shuffle(), 49–50
trace(), 1–2, 11–14
G
game view, 95–105
GameView class, 95–105
Android games, 347–349
Flare3D, 389–391
game-over views
creating, 149–172
GameOverView class, 95. See also
end view
Android games, 359–360
Flare3D, 391–392
iOS games, 302–304
GameOverView_Pool class (iOS
games), 304–305
GameOverView class, 95. See also
end view
Android games, 359–360
Flare3D, 391–392
iOS games, 302–304
GameOverView_Pool class (iOS
games), 304–305
games
3D (Stage3D/Flare3D)
3D particle effects, 421–435
animating objects, 393–401
astro character, 393–401
cameras, 393–401
collision detection, 401–410
cost, 384
Data class, 392
downloading, 384
energy 3D model, 393–401
example directory, 421–435
fans, 393–401
files, 384–386
first-person view, 421–435
GameOverView class,
391–392
GameView class, 389–391
initializing world, 387–392
IntroView class, 388–389
libraries, 384–386
Main class, 387–388
mines, 393–401
opening, 386
overview, 383–384
ParticleEmitter3D class,
421–435
preloader display, 393–401
score, 411–420
shadows, 411–420
sound, 411–420
terminology, 384
Android. See Android games
creating process, 86
iOS. See iOS games
multiplayer games. See
multiplayer games
pausing, 74
registering
Facebook, 438–444
Google+, 513–518
restarting, 74
social networks. See social networks
states, storing, 76
tank game overview, 85
Twitter, registering, 492–494
Yellow Planet. See Flare3D
GameView class, 95–105
Android games, 347–349
Flare3D, 389–391
gc (garbage collection), 57
listeners, 57–58
overview, 57
preparing objects, 135–149
603
604
Index
General tab
Air for Android, 374–376
Air for iOS, 323–327
geometry
adjacent angles, 62
angles of reflection, 62–64
overview, 61–62
Pythagorean Theorem, 64–65
straight angles, 61
sum of triangle’s angles, 61
vertical angles, 62
getLocal() function, 76
getters, 34–39
getTimer() function, 57
Google+
ActionScript 3.0 library, 533–552
API
ActionScript, 513
authorizing request, 517–533
JavaScript injection, 517–533
overview, 513
sample Flash applications,
518–533
badges, 509–510
buttons
+1, 509
code, 510–513
Share, 510
overview, 508
plug-ins, 510–513
registering games, 513–518
SWC, 551–552
gotoAndPlay method, 591–592
gotoAndStop method, 591–592
GPU usage. See CPU/GPU usage
H
Hashtag button (Twitter), 485
hit detection. See collision
detection
hitTestObject() method, 92–93
hitTestPoint() method, 92–93
I
Icons tab
Air for Android, 380
Air for iOS, 330
Implicit Coercion of a value of
Type xxxx to an
Unrelated Type yyyy
error message, 579
Implicit Coercion of a value with
Static Type xxxx to a
Possibly Unrelated Type
yyyy error message,
579–580
importing classes, 29, 31
injection (javascript), 517–533
input (keyboards), 88–91
instances, tweening, 76–77
internal methods, 31–33, 42–43
internal properties, 31–33
interpolation, linear, 65–67
intializing worlds (Flare3D),
387–392
introduction view, 95–105
IntroView class, 95–105, 172–192
Android games, 342–346
Flare3D, 388–389
iOS games, 262–271
IntroView_Pool class (iOS
games), 271–272
IntroView class, 95–105, 172–192
Android games, 342–346
Flare3D, 388–389
iOS games, 262–271
IntroView_Pool class (iOS games),
271–272
iOS games
collision detection, 260
CombatView class, 274–279
CombatView_Pool class,
279–280
Controller class, 260, 280–296
Controller_Pool class, 297
Data class, 305–308
display size, 259
distribution certificates, 313,
331–335
distribution provisioning profile,
313, 331–335
EnemyTank class, 301–302
EnemyTank_Pool class, 302
GameOverView class, 302–304
GameOverView_Pool class,
304–305
IntroView class, 262–271
IntroView_Pool class, 271–272
Main class, 260–262
managing memory, 260
mouse, 259
moving tanks, 260
overview, 259–260
PlayerTank class, 299–300
PlayerTank_Pool class, 300–301
publishing
Air for iOS Deployment tab,
327–330
Air for iOS General tab,
323–327
Air for iOS Icons tab, 330
Air for iOS Languages tab,
330–331
development certificates,
312–323
development provisioning
profile, 312–323
Slider class, 272–274
sliders, 259
sound, 260
Tank class, 298–299
testing, 308–312
Timer class, 297
TimerExt class, 297
touch events, 259
iPad. See iOS games
J
JavaScript
ActionScript communication,
56–57, 445–451, 478
injection (Google+ API), 517–533
JavaScript API (Facebook)
ActionScript communication,
446–451
appID, 451–452
Dialogs, 453–466
FB methods, 453
JavaScript SDK, 452
overview, 444–446
permissions, 453
social graph, 466–477
JavaScript SDK (Facebook
JavaScript API), 452
K
KBcontrols class, 96–105
Keyboard class, 88–89
KeyboardEvent class, 89–91
keyboards
event listeners, 89–91, 104–105
input, 88–91
KBcontrols class, 96–105
Index
key-press combinations, 110–114
Keyboard class, 88–89
KeyboardEvent class, 89–91
KeyboardType class, 88–89
multiple keydown presses,
135–149
objects, moving, 91–105
repeat delay, 104–110
KeyboardType class, 88–89
keydown presses, multiple,
135–149
key-press combinations, 110–114
L
Label Must Be a Simple Identifier
error message, 579
Languages tab
Air for Android, 381–382
Air for iOS, 330–331
libraries
Flare3D, 384–386
Google+ ActionScript 3.0 library,
533–552
Tweetr ActionScript library,
494–508
Light_Pool class (Android games),
366–367
line numbers, debugging, 3
linear interpolation, 65–67
listeners (events)
CPU/GPU usage, 235, 256
dispatchers, 38–39, 57–58
keyboards, 89–91, 104–105
managing memory, 211
remote, 235
location. See position
logic games. See Android games
loops
break statements, 60
chunks, 58–60
CPU/GPU usage, 231–233
do, 58–60
enterFrame, 60–61, 124–125
for, 58–60
overview, 58
setInterval(), 60–61
single-multiple comparison,
124–125
Timer, 60–61
troubleshooting lost object
references, 592–593
while, 58–60
lost object references, 592–593
M
Main class
Android games, 339–342
creating, 81–82
Flare3D, 387–388
iOS games, 260–262
organizing, 95–105
peer-to-peer multiplayer games,
554–569
managing
CPU/GPU usage. See CPU/GPU
usage
memory, 197–199
callback functions, 205–206
dispatchEvent, 205–206
display objects, 206
filters, 206
iOS games, 260
listeners, 211
pooling objects, 206–208
reusing objects, 208
sound, 208–210
timers, 210–211
math
CPU/GPU usage
arithmetic, 212–218
bitwise operators, 212–218
division, 212–213
Math class, 215–218
modulo operator, 214
multiplication, 213–214
geometry
adjacent angles, 62
angles of reflection, 62–64
overview, 61–62
Pythagorean Theorem,
64–65
straight angles, 61
sum of triangle’s angles, 61
vertical angles, 62
linear interpolation, 65–67
Math class (CPU/GPU usage),
215–218
modulo operator, 67–68
overview, 61
randomization, 68–69
trigonometry, 69–74
Math class (CPU/GPU usage),
215–218
measuring performance, 194–195
memory. See also performance
CPU/GPU usage relationship,
195–197
gc (garbage collection), 57
listeners, 57–58
overview, 57
preparing objects, 135–149
managing, 197–199
callback functions, 205–206
dispatchEvent, 205–206
display objects, 206
filters, 206
iOS games, 260
listeners, 211
pooling objects, 206–208
reusing objects, 208
sound, 208–210
timers, 210–211
tracking, 199–203
usage, 199–203
Mention button (Twitter),
485–486
messages. See error messages
methods. See also functions
addChild(), 87
FB (Facebook JavaScript
API), 453
gotoAndPlay, 591–592
gotoAndStop, 591–592
hitTestObject(), 92–93
hitTestPoint(), 92–93
internal, 31–33, 42–43
private, 31–33, 42–43
protected, 31–33, 42–43
public, 31–33, 37, 42–43
static, 42–43
mines (Flare3D), 393–401
mobile simulators, 308–312
models
Flare3D energy 3D model,
393–401
Stage3D. See Stage3D
modulo operator (%), 67–68, 214
mouse
CPU/GPU usage, 233–235
creating custom cursors, 114–118
events, 74
iOS games, 259
moving, 118–124
rollout events, 118–124
MovieClip class, extending, 85–86
605
606
Index
MovieClips
MovieClip class, extending,
85–86
scope, 19–23
moving
mouse, 118–124
objects
angles of reflection, 62–64
boundary violations, 110–114,
149–172
collision detection, 149–172
keyboard, 91–95
keyboards, 96–105
stuttering, 104–110
trigonometry, 69–74
tanks (iOS games), 260
Mudbubble Boy test, 250–255
multiplayer games. See also social
networks
overview, 553
peer-to-peer
Data class, 572–574
Main class, 554–569
NetGroup class, 569–572
overview, 554
P2P class, 569–572
server-based, 553–554
multiple
enterFrame loops, compared to
single loop, 124–125
keydown presses, 135–149
multiplication (CPU/GPU usage),
213–214
N
names
classes, 30
variables, 78
NetGroup class, 569–572
No Active Security Context error
message, 585
No Default Constructor Found in
Base Class xxx error
message, 585
notation (arrays), 50–51
numbers
errors, 16
randomization, 68–69
Twitter requests per hour
limits, 491
O
object-oriented programming. See
OOP
objects
associative arrays, 50
collision detection, 51–54,
401–410
color, 51–54
creating, 85, 125–135
Dictionary, 55
display objects, 206
distance, calculating, 64–65
Flare3D
animating, 393–401
collision detection, 401–410
shadows, 411–420
gc, preparing, 135–149
linear interpolation, 65–67
managing memory, 206–208
moving
angles of reflection, 62–64
boundary violations, 110–114,
149–172
collision detection, 149–172
keyboards, 91–105
stuttering, 104–110
trigonometry, 69–74
OOP (object-oriented
programming), 18
class encapsulation, 95–105
overview, 18–19
pooling objects, 206–208
position
overlapping, 92–93
randomness, 90–91
properties, 65–67
registration points, 74–76, 85
reusing objects, 208
rotating
creating, 114–118
synchronizing, 118–124
scrolling, 65–67
SharedObject, 76
stages, finding, 87–88
tanks. See tanks
timelines, 65–67
troubleshooting lost references,
592–593
vectors, 78–79
viewing, overlapping, 87–88
online debugging, 2
OOP (object-oriented
programming), 18
class encapsulation, 95–105
overview, 18–19
opening Flare3D, 386
operators (CPU/GPU usage)
bitwise operators, 212–218
modulo operator, 67–68, 214
optimizing performance, 193,
203–205
organizing
data, 172–192
Main class, 95–105
output, trace() function, 2
overlapping objects
position, 92–93
viewing, 87–88
P
P2P class, 569–572
packages
classes, 29, 31
Flash API, 82–84
Parameter Text Must Be Non-Null
error message, 585
parameters, controlling, 172–192
partial blitting (CPU/GPU usage),
228–231
particle effects (Flare3D),
421–435
ParticleEmitter3D class (Flare3D),
421–435
passing variables, 36–37
Patch the Scarecrow test, 247–250
pausing games, 74
peer-to-peer multiplayer games
Data class, 572–574
Main class, 554–569
NetGroup class, 569–572
overview, 554
P2P class, 569–572
performance
CPU/GPU usage. See CPU/GPU
usage
development/target platforms,
194–195
measuring, 194–195
memory. See memory
optimizing, 193, 203–205
testing, 199–203
Index
permissions
Air for Android Permissions tab,
380–381
Facebook JavaScript API, 453
Permissions tab (Air for Android),
380–381
permit debugging, 2–8
pixels, 51–54
plane geometry. See geometry
platforms (performance), 194–195
PlayerTank class, 96–105. See also
tanks
creating, 85–86
iOS games, 299–300
PlayerTank_Pool class (iOS
games), 300–301
plug-ins (Google+), 510–513
points (registration points), 74–76,
85
pooling objects (managing
memory), 206–208
position (objects)
overlapping, 92–93
randomness, 90–91
preloader display (Flare3D),
393–401
preparing objects (gc), 135–149
private methods, 31–33, 42–43
private properties, 31–33
process, creating games, 86
profiles
custom profiles, debugging, 8–9
publishing iOS games
development provisioning
profiles, 312–323
distribution provisioning
profiles, 313, 331–335
properties
cacheAsBitmap, 219–220
cacheAsBitmapMatrix, 219–220
CPU/GPU usage, 219–220
frameRate, 74
getters, 34–39
internal, 31–33
objects, linear interpolation,
65–67
private, 31–33
protected, 31–33
public, 31–33
setters, 34–39
Stage class, 90
static, 39–41
undeclared, assigning, 45–46
protected methods, 31–33, 42–43
protected properties, 31–33
provisioning profiles (publishing
iOS games)
development provisioning
profiles, 312–323
distribution provisioning profiles,
313, 331–335
pseudo classes, 97
public methods, 31–33, 37, 42–43
public properties, 31–33
publishing
Android games (Air for Android)
Deployment tab, 377–380
General tab, 374–376
Icons tab, 380
Languages tab, 381–382
Permissions tab, 380–381
iOS games (Air for iOS)
Deployment tab, 327–330
development certificates,
312–323
development provisioning
profile, 312–323
General tab, 323–327
Icons tab, 330
Languages tab, 330–331
puzzle games. See Android games
Pythagorean Theorem, 64–65
R
random() function, 68–69
randomization, numbers, 68–69
randomness, object position, 90–91
references, lost, 592–593
reflecting (angles of reflection),
62–64
registering games
Facebook, 438–444
Google+, 513–518
Twitter, 492–494
registration points (objects), 74–76, 85
remote listeners (CPU/GPU usage),
235
rendering model. See Stage3D
repeat delay (keyboards), 104–110
repeatedly executed code,
troubleshooting, 594
Replay_Btn class (Android
games), 363
requests
Google+ API, 517–533
Twitter
number per hour limits, 491
that do not require
authentication, 486–491
that require authentication,
492–508
restarting games, 74
reusing objects (managing
memory), 208
rollout events (mouse movement),
118–124
rotating objects
creating, 114–118
synchronizing, 118–124
run-time errors, 3–8
S
sample Flash applications (Google+
API), 518–533
saving classes (SWF), 87
scope
classes, 23–24
debugging, 17
errors, 17
functions, 24–26
MovieClips, 19–23
overview, 18–19
score (Flare3D), 411–420
Scout, 194–195
scrolling objects, linear
interpolation, 65–67
SDKs (JavaScript SDK), 452
security, social networks, 437–438
Security Sandbox Error error
message, 586
SecurityError Error #2000 No
Active Security Context
error message, 585
server-based multiplayer games,
553–554
setInterval() loops, 60–61
setters, 34–39
shadows (Flare3D), 411–420
Share a Link button (Twitter), 484
Share button (Google+), 510
SharedObject object, 76
607
608
Index
shooting functionality
creating, 114–118
troubleshooting, 149–172
shuffle() function, 49–50
shuffling (arrays), 49–50
significant timeline coding, 17–18
simulators, mobile, 308–312
single enterFrame loops, compared
to multiple, 124–125
singletons (classes), 43–45
size
display size (iOS games), 259
stages, 90
Slider class
Android games, 363–365
iOS games, 272–274
sliders
iOS games, 259
Slider class
Android games, 363–365
iOS games, 272–274
social games. See multiplayer
games; social networks
social graph (Facebook JavaScript
API), 466–477
social networks
Facebook. See Facebook
Google+. See Google+
multiplayer games. See
multiplayer games
overview, 437–438
security, 437–438
Twitter. See Twitter
sound
creating, 125–135, 149–172
Flare3D, 411–420
iOS games, 260
managing memory, 208–210
stage
blitting (CPU/GPU usage),
220–228
focus, troubleshooting, 135–149
Stage class, 90
Stage class, 90
Stage3D
CPU/GPU usage
overview, 235–236
Starling 10,000 MovieClips
test, 242–247
Starling Mudbubble Boy test,
250–255
Starling Patch the Scarecrow
test, 247–250
Starling/blitting comparison,
236–256
Flare3D
3D particle effects, 421–435
animating objects, 393–401
astro character, 393–401
cameras, 393–401
collision detection, 401–410
cost, 384
Data class, 392
downloading, 384
energy 3D model, 393–401
example directory, 421–435
fans, 393–401
files, 384–386
first-person view, 421–435
GameOverView class,
391–392
GameView class, 389–391
initializing world, 387–392
IntroView class, 388–389
libraries, 384–386
Main class, 387–388
mines, 393–401
opening, 386
overview, 383–384
ParticleEmitter3D class,
421–435
preloader display, 393–401
score, 411–420
shadows, 411–420
sound, 411–420
terminology, 384
stages
finding objects, 87–88
size, 90
stage blitting (CPU/GPU usage),
220–228
tiling, 67–68
Starling
10,000 MovieClips test, 242–247
blitting comparison, 236–256
Mudbubble Boy test, 250–255
Patch the Scarecrow test, 247–250
statements, break, 60
states, storing, 76
static methods, 42–43
static properties, 39–41
statistics displays, 149–172
storing
games, states, 76
gc (garbage collection), 57
listeners, 57–58
overview, 57
preparing objects, 135–149
straight angles, 61
strings (arrays), 48–49
strong listeners (managing
memory), 211
stuttering objects, 104–110
sum of triangle’s angles, 61
SWC (Google+), 551–552
SWF
names, errors, 7–8
saving classes, 87
Switch class (Android games), 362
Switch_Pool class (Android
games), 366
Switcher. See Android games
symbols (tanks), 125–135
synchronizing, rotating objects,
118–124
T
Tank class (iOS games), 298–299
tank game. See games
tanks. See also objects
drawing, 85
EnemyTank class, 96–105. See
also tanks
creating, 86, 125–135
iOS games, 301–302
EnemyTank_Pool class (iOS
games), 302
moving (iOS games), 260
PlayerTank class, 96–105. See also
tanks
creating, 85–86
iOS games, 299–300
PlayerTank_Pool class (iOS
games), 300–301
shooting
creating functionality,
114–118
troubleshooting functionality,
149–172
symbols, 125–135
Tank class (iOS games), 298–299
turrets
Index
creating, 114–118
synchronizing, 118–124
target frames, troubleshooting,
591–592
target/development platform
comparison, 194–195
templates, debugging, 18
terminology (Flare3D), 384
testing. See also debugging; errors
Android games
ADB (Android Debug
Bridge), 371–372
ADT (Adobe AIR Developer
Tool), 372–374
AIR Debug Launcher, 368
emulators, 368–371
overview, 367
experimenting, 14–16
iOS games, 308–312
performance, 199–203
textfields, debugging, 1–2
TF class (Android games), 362–363
TF_Pool class (Android games), 367
The Class xxx Must Subclass yyy
Since It Is Linked to a
Library Symbol of That
Type error message, 586
The Name of Definition Xxxx Does
Not Reflect the Location
of This File. Please
Change the Definition
Name inside This File, or
Rename the File. error
message, 586
The Private Attribute May Be Used
Only on Class Property
Definitions error
message, 578
The SWF file file…someswf.swf
Contains Invalid Data
error message, 586
tiling (stages), 67–68
time, Twitter requests per hour
limits, 491
timelines
coding, 17–18
objects, linear interpolation,
65–67
Timer class (iOS games), 297
Timer loops, 60–61
TimerExt class (iOS games), 297
timers
managing memory, 210–211
Timer class (iOS games), 297
Timer loops, 60–61
TimerExt class (iOS games), 297
tools, debugging, 1–2
touch events (iOS games), 259
trace() function, 1–2, 11–14
tracking memory, 199–203
triangles
Pythagorean Theorem, 64–65
sum of triangle’s angles, 61
trigonometry, 69–74
troubleshooting
class files, 595
mouse movement, 118–124
multiple keydown presses,
135–149
shooting functionality, 149–172
stage focus, 135–149
turrets (tanks)
creating, 114–118
synchronizing, 118–124
Tween class, 76–77
tweening instances, 76–77
TweenLite class, 76–77
Tweetr ActionScript library,
494–508
Twitter
buttons
Follow, 485
Hashtag, 485
Mention, 485–486
Share a Link, 484
overview, 482–484
registering games, 492–494
requests
number per hour limits, 491
that do not require
authentication, 486–491
that require authentication,
492–508
Tweetr ActionScript library,
494–508
Type Was Not Found or Was Not a
Compile-Time Constant
error message, 578–579
TypeError Error #2007 Parameter
Text Must Be Non-Null
error message, 585
U
undeclared properties, 45–46
Unhandled IOErrorEvent.
text=Error #2035
URL Not Found error
message, 586
URL Not Found error message, 586
usage (memory), 199–203
users, controlling parameters,
172–192
V
variables
CPU/GPU usage, 256
names, 78
passing, 36–37
Vector class, 78–79
vectors
CPU/GPU usage, 256–258
objects, 78–79
Vector class, 78–79
vertical angles, 62
viewing overlapping objects, 87–88
views/displays
creating, 125–135
end view, 95. See also
GameOverView class
Flare3D
first-person view, 421–435
preloader display, 393–401
game view. See game view
game-over. See game-over views
introduction. See introduction
view
objects, managing memory, 206
size (iOS games), 259
stage, troubleshooting focus,
135–149
statistics, creating, 149–172
virtual device emulator, 194
W–Y
weak listeners (managing memory),
211
while loops, 58–60
worlds (Flare3D), 387–392
Yellow Planet. See Flare3D
609