В данном уроке мы расширим возможности созданной в предыдущем уроке игры. А именно: 
 - Разнообразим отскок от рокетки в зависимости от места попадания мячом 
 - Сделаем ведение счета игры 2-мя способами: средствами среды Flex и AS3 
 - Добавим эффект свечения при ударе мяча  Открываем наш проект. В файле Main.mxml находим функцию движения мяча moveBall. 
 Находим строчку 
 
Code
 
 if (ball.hitTestObject(player)) { 
  yDir *= -1; 
 } 
 
 Для того, чтобы добавить особое поведение мячу при столкновении с рокеткой, мы добавим новую функцию, призванную просчитать место столкновения и в зависимости от этого места изменять скорость и угол отражения: 
 
Code
 
 if (ball.hitTestObject(player)) { 
  yDir *= -1; 
  [b]checkHitPosition(player);[/b] /// параметром данной функции является рокетка стлокновения с которой мы анализируем. в данном случае это игрок 
 } 
 
 Теперь подробно рассмотрим функцию проверки места столкновения: 
 
Code
 
 private function checkHitPosition(paddle:Paddle):void 
 { 
  var hitPercent:Number; 
                 /*переменная hitPercent отражает место в которое ударился мяч, 0 - это левый край рокетки, 50 - это ее середина, 100 - правый край 
  var ballPosition:Number = ball.x - paddle.x; /// эта переменная отражает положение мяча относительно положения рокетки 
     
  hitPercent = (ballPosition / (paddle.width - ball.width)) - .5; //// данная формула вычисляет место удара 
  xDir = hitPercent * 20;  /// тут мы выбираем под каким углом отразится мяч, мне понравился коэффициент 20, можете поперебирать и выбрать понравившийся себе вариант 
  yDir *= 1.04; /// добавляем ускорение мячу 
 } 
 
 Те же самые действия необходимо повторить и для столкновения с рокеткой соперника. 
 Теперь давайте сделаем игру более осмысленной добавим ведение счета, дав, таким образом, возможность выигрывать. Я решил продемонстрировать 2 альтернативных способа сделать это. На то есть 2 причины: 
 - Знать надо оба способа, в процессе работы во Флекс, у Вас рано или поздно возникнет ситуация, когда стандартные средства (кнопки, картинки, лайбелы) перестанут удовлетворять Вашим целям, и лучшим выходом будет создание нужного элемент лично. 
 - В процессе планирования Вы сможете, четко представив назначение данного элемента, выбрать верную реализацию. Использовать встроенные элементы флекса легче, и если они удовлетворяют нужной функциональности, то использовать лучше именно их. 
 К делу. Переходим в Main.mxml к Disign View. Находим вкладку Components -> Controls и перетаскиваем оттуда на сцену компонент label. Возвращаемся в Source View и приводим наш компонент к следующему виду: 
 
Code
 
 <mx:Label id="escore" text="{enemyScore.toString()}" fontFamily="Arial" fontSize="24" x="15" y="15" width="60" height="35"/> 
 
 Обратите внимание на строку text="{enemyScore.toString()}" здесь мы используем привязку данных для атрибута text компонента label. Это означет, что в качестве строкового значения будет использоваться значение переменной enemyScore и любоей изменение ее значения повлечет изменение текста. 
 Самое время теперь добавить эту глобальную переменную: 
 
Code
 
 [Bindable]  /// данный тег указывает на то, что переменная используется в привязке данных (data binding) 
 private var enemyScore:int = 0; 
 
 Это пример использования средств Флекс. Теперь рассмотрим использование AS3 для данной цели. Нам нужно создать новый класс, выполняющий те же самые функции. Создаем новый класс, назовем его ScreenText и расширим его от класса Sprite. 
 Вот полный код ScreenText.as с комментариями: 
 
Code
 
 package   
 { 
  import flash.display.Sprite; 
  import flash.text.TextField; 
  import flash.text.TextFormat; 
   
 public class ScreenText extends Sprite 
 { 
  protected var displayText:TextField;  //// экземпляр класса текст 
  protected var format:TextFormat;     //// экземпляр клааса форматирования класса 
    
  public function ScreenText()  
  { 
   displayText = new TextField(); 
   displayText.text = "0";                  //// задаем первоначальный текст счета "0" 
   displayText.selectable = false;       //// делаем текст "невыбираемым" 
   displayText.autoSize;                    //// авторазмер 
   displayText.x = 0; 
   displayText.y = 0; 
     
   format = new TextFormat("Arial", 24);    //// задаем шрифт и размер 
   displayText.setTextFormat(format);       //// атачим созданный формат текстовому полю 
     
   addChild(displayText); 
  } 
    
  public function setScore(newscore:String):void    //// здесь мы создадим публичную (общедоступную) функцию изменения текста 
  { 
   this.displayText.text = newscore; 
   format = new TextFormat("Arial", 24); 
   displayText.setTextFormat(format); 
  } 
    
 } 
 } 
 
 Теперь осталось добавить экземпляр ScreenText'а в нашу игру: 
 
Code
 
 private var scoreLabel:ScreenText;    /// создаем компоненту 
 private var playerScore:int = 0;        /// создаем переменную счета для игрока 
 private function init():void 
 { 
  Mouse.hide(); 
     
  player = new Paddle([0x00FF00, 0x0000FF]); 
  player.x = 300; 
  player.y = 430; 
  stage.addChild(player); 
     
  enemy = new Paddle([0xFF0000, 0xFF00FF]); 
  enemy.x = 300; 
  enemy.y = 50; 
  stage.addChild(enemy); 
     
  player.addEventListener(Event.ENTER_FRAME, movePlayer); 
  enemy.addEventListener(Event.ENTER_FRAME, moveEnemy); 
     
  ball = new Ball(); 
  ball.x = 350; 
  ball.y = 410; 
  stage.addChild(ball); 
     
  [b]scoreLabel = new ScreenText(); /// инициируем 
  scoreLabel.x = 15;                         /// задаем местоположение 
  scoreLabel.y = 450; 
  stage.addChild(scoreLabel);           /// обратите внимание на то что счет должен находить в самом верху списка отображения и перекрывать все игровые элементы, поэтому мы добавляем его на сцену последним[/b] 
     
  ball.addEventListener(Event.ENTER_FRAME, moveBall); 
 } 
 
 Теперь мы имеем совершенно идентичные стороннему человеку компоненты созданные и работающие по-разному. 
 Осталось только добавить возможности прогирывать и выигрывать "сеты". 
 Для этого возвращаемся к функции движения мяча moveBall и меняем 
 
Code
 
 if (ball.y <= 0) { 
  yDir *= -1; 
 } 
 else if (ball.y >= this.height - ball.height/2) { 
  yDir *= -1; 
 } 
 
 на 
 
Code
 
 if (ball.y <= 0) {                            //// если мяч коснулся верхней части экрана 
  ++playerScore;            //// увеличиваем счет игрока 
  scoreLabel.setScore(playerScore.toString());   //// вызываем публичную функцию класса ScreenText и задем ей в качесвте параметра новый счет игрока 
  resetBallPosition();     //// теперь надо начать новый "сет" при помощи вызова данной функции 
 } 
 else if (ball.y >= this.height - ball.height/2) {     //// если мяч коснулся нижней части экрана 
  ++enemyScore;        //// увеличиваем лицевой счет компьютера, из-за привязки данных, увеличения значения переменной автоматически повлечет за собой увеличение значения тесктового поля созданой нами компаненты Label 
  resetBallPosition(); 
 } 
 
 Ну и простая функция новой игры, просто возвращающей первоначальные позиции элемнтам игры: 
 
Code
 
 private function resetBallPosition():void 
 { 
  enemy.x = 300; 
  ball.x = 350; 
  ball.y = 410; 
  xDir = 10; 
  yDir = -10; 
 } 
 
 Ну и напоследок давайте добавим в нашу игру спецэффектов)) 
 Откроем файл Ball.as и внесем ряд изменений в наш класс. Добавим 2 внутренние функции: 
 
Code
 
 public function glowBall(color:Array):void               /// в параметр функции подсветки мяча добавим массив цветов, для того чтобы иметь возможности присваивать различные цвета в зависимости от наших целей 
 { 
  var gradientGlow:GradientGlowFilter = new GradientGlowFilter();  /// создаем новый экземпляр класса градиентного свечения 
  gradientGlow.distance = 0;                    /// задаем различные параметры, поиграейте с ними, чтобы понять, что есть что  
  gradientGlow.angle = 45; 
  gradientGlow.colors = color; 
  gradientGlow.alphas = [0, 1]; 
  gradientGlow.ratios = [0, 255]; 
  gradientGlow.blurX = 50; 
  gradientGlow.blurY = 50; 
  gradientGlow.strength = 4; 
  gradientGlow.quality = BitmapFilterQuality.MEDIUM; 
  gradientGlow.type = BitmapFilterType.OUTER; 
  this.filters = [gradientGlow];                    //// эффекты добавляются во внутренний маасив класса Sprite [b]filters[/b] 
     
  var timer:Timer = new Timer(100, 1);                        //// создаем таймер отсчитывающий 100 милисекунд один раз 
  timer.addEventListener(TimerEvent.TIMER_COMPLETE, unGlowBall);  //// по завершению таймера вызываем функцию затухания 
  timer.start();   //// стартуем 
 } 
    
 protected function unGlowBall(e:TimerEvent):void 
 { 
  this.filters = null;    //// "нуллим" массив тем самым убирая все эффекты 
 } 
 
 Обратите внимание на то, как осуществляется анимация свечения в данном случае. По скольку положение мяча просчитывается каждый кадр, то столкновение длиться не больше чем 1 секунда / 24 кадра в секунду. "Маловато будет!" (С). Поэтому мы используем анимацию по времени. Свечение в данном случае пропадет через 100 милисекунд после появления. Это обеспечит созданный нами таймер. 
 Теперь непосредственно добавим подсветку при столкновении мяча с препятствиями внутри функции moveBall: 
 
Code
 
 if (ball.x <= 0) { 
  xDir *= -1; 
  ball.glowBall([0x000000, 0x0000FF]); /// синяя подсветка для столкновения со стенками 
 } 
 else if (ball.x >= this.width - ball.width/2) { 
  xDir *= -1; 
  ball.glowBall([0x000000, 0x0000FF]); 
 } 
 if (ball.hitTestObject(player)) { 
  yDir *= -1; 
  ball.glowBall([0x000000, 0x00FF00]);  /// зеленая подсветка для столкновения с рокеткой игрока 
  checkHitPosition(player); 
 } 
 else if (ball.hitTestObject(enemy)) { 
  yDir *= -1; 
  ball.glowBall([0x000000, 0xFF0000]);  /// красная подсветка для столкновения с рокеткой компьютера 
  checkHitPosition(enemy); 
 } 
 
 Полный код Main.mxml (с измененными размерами экрана): 
 
Code
 
 <?xml version="1.0" encoding="utf-8"?> 
 <mx:Application layout="absolute" width="700" height="500" frameRate="24" applicationComplete="init()" xmlns:mx="http://www.adobe.com/2006/mxml"> 
     <mx:Script><![CDATA[ 
 import flash.events.Event; 
 import flash.events.MouseEvent; 
   private var ball:Ball; 
   private var player:Paddle; 
   private var enemy:Paddle; 
   private var xDir:int = 10; 
   private var yDir:int = -10; 
   private var targetX:Number = 0; 
   private var easing:Number = 3; 
   private var scoreLabel:ScreenText; 
   private var playerScore:int = 0; 
   [Bindable] 
   private var enemyScore:int = 0; 
    
   private function init():void 
   { 
    Mouse.hide(); 
     
    player = new Paddle([0x00FF00, 0x0000FF]); 
    player.x = 300; 
    player.y = 430; 
    stage.addChild(player); 
     
    enemy = new Paddle([0xFF0000, 0xFF00FF]); 
    enemy.x = 300; 
    enemy.y = 50; 
    stage.addChild(enemy); 
     
    player.addEventListener(Event.ENTER_FRAME, movePlayer); 
    enemy.addEventListener(Event.ENTER_FRAME, moveEnemy); 
     
    ball = new Ball(); 
    ball.x = 350; 
    ball.y = 410; 
    stage.addChild(ball); 
     
    scoreLabel = new ScreenText(); 
    scoreLabel.x = 15; 
    scoreLabel.y = 450; 
    stage.addChild(scoreLabel); 
     
    ball.addEventListener(Event.ENTER_FRAME, moveBall); 
   } 
    
   private function resetBallPosition():void 
   { 
    enemy.x = 300; 
    ball.x = 350; 
    ball.y = 410; 
    xDir = 10; 
    yDir = -10; 
   } 
    
   private function checkHitPosition(paddle:Paddle):void 
   { 
    var hitPercent:Number; 
    var ballPosition:Number = ball.x - paddle.x; 
     
    hitPercent = (ballPosition / (paddle.width - ball.width)) - .5; 
    xDir = hitPercent * 20; 
    yDir *= 1.04; 
   } 
    
   private function moveBall(e:Event):void 
   { 
     
    if (ball.x <= 0) { 
     xDir *= -1; 
     ball.glowBall([0x000000, 0x0000FF]); 
    } 
    else if (ball.x >= this.width - ball.width/2) { 
     xDir *= -1; 
     ball.glowBall([0x000000, 0x0000FF]); 
    } 
    if (ball.hitTestObject(player)) { 
     yDir *= -1; 
     ball.glowBall([0x000000, 0x00FF00]); 
     checkHitPosition(player); 
    } 
    else if (ball.hitTestObject(enemy)) { 
     yDir *= -1; 
     ball.glowBall([0x000000, 0xFF0000]); 
     checkHitPosition(enemy); 
    } 
    if (ball.y <= 0) { 
     ++playerScore; 
     scoreLabel.setScore(playerScore.toString()); 
     resetBallPosition(); 
    } 
    else if (ball.y >= this.height - ball.height/2) { 
     ++enemyScore; 
     resetBallPosition(); 
    } 
     
    ball.x += xDir; 
    ball.y += yDir; 
   } 
   private function movePlayer(e:Event):void 
   { 
    if (this.mouseX < player.width/2) { 
     targetX = 0; 
    } 
    else if(this.mouseX > this.width - player.width/2) { 
     targetX = this.width - player.width; 
    } 
    else { 
     targetX = this.mouseX - player.width/2; 
    } 
      
    player.x += (targetX/2 - player.x/2) / easing;  
   } 
    
   private function moveEnemy(e:Event):void 
   {  
    var enemyTargetX:Number; 
    enemyTargetX = ball.x - enemy.width / 2; 
     
    if (enemyTargetX <= 0) { 
     enemyTargetX = 0; 
    } 
    else if (enemyTargetX >= this.width - enemy.width / 2) { 
     enemyTargetX = this.width - enemy.width / 2; 
    } 
     
    enemy.x += (enemyTargetX / 2 - enemy.x / 2) / easing; 
   } 
   ]]></mx:Script> 
     <mx:Label id="escore" text="{enemyScore.toString()}" fontFamily="Arial" fontSize="24" x="15" y="15" width="60" height="35"/> 
 </mx:Application> 
 
 Полный код ScreenText.as: 
 
Code
 
 package   
 { 
  import flash.display.Sprite; 
  import flash.text.TextField; 
  import flash.text.TextFormat; 
   
  public class ScreenText extends Sprite 
  { 
   protected var displayText:TextField; 
   protected var format:TextFormat; 
    
   public function ScreenText()  
   { 
    displayText = new TextField(); 
    displayText.text = "0"; 
    displayText.selectable = false; 
    displayText.autoSize; 
    displayText.x = 0; 
    displayText.y = 0; 
     
    format = new TextFormat("Arial", 24); 
    displayText.setTextFormat(format);// .defaultTextFormat = format; 
     
    addChild(displayText); 
   } 
    
   public function setScore(newscore:String):void 
   { 
    this.displayText.text = newscore; 
    format = new TextFormat("Arial", 24); 
    displayText.setTextFormat(format); 
   } 
    
  } 
 } 
 
 Полный код Ball.as: 
 
Code
 
 package   
 { 
  import flash.display.*; 
  import flash.events.TimerEvent; 
  import flash.geom.Matrix; 
  import flash.filters.BitmapFilterQuality; 
  import flash.filters.BitmapFilterType; 
  import flash.filters.GradientGlowFilter; 
  import flash.utils.Timer; 
   
  public class Ball extends Sprite 
  { 
    
   public function Ball()  
   { 
    var matr:Matrix = new Matrix(); 
    matr.createGradientBox(15, 15, 0, -5, -5); 
     
    this.graphics.lineStyle(1); 
    this.graphics.beginGradientFill(GradientType.RADIAL, [0xFF0000, 0xFFC000], [1, 1], [50, 255], matr, SpreadMethod.REFLECT); 
    this.graphics.drawCircle(0, 0, 15); 
    this.graphics.endFill(); 
   } 
    
   public function glowBall(color:Array):void 
   { 
    var gradientGlow:GradientGlowFilter = new GradientGlowFilter(); 
    gradientGlow.distance = 0; 
    gradientGlow.angle = 45; 
    gradientGlow.colors = color; 
    gradientGlow.alphas = [0, 1]; 
    gradientGlow.ratios = [0, 255]; 
    gradientGlow.blurX = 50; 
    gradientGlow.blurY = 50; 
    gradientGlow.strength = 4; 
    gradientGlow.quality = BitmapFilterQuality.MEDIUM; 
    gradientGlow.type = BitmapFilterType.OUTER; 
    this.filters = [gradientGlow]; 
     
    var timer:Timer = new Timer(100, 1); 
    timer.addEventListener(TimerEvent.TIMER_COMPLETE, unGlowBall); 
    timer.start(); 
   } 
    
   protected function unGlowBall(e:TimerEvent):void 
   { 
    this.filters = null; 
   } 
    
  } 
 } 
 
 Компилим, играем. 
 B1z ©