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