В данном уроке мы расширим возможности созданной в предыдущем уроке игры. А именно: - Разнообразим отскок от рокетки в зависимости от места попадания мячом - Сделаем ведение счета игры 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 ©
|