ИГРОДЕЛ
Войдите на сайт или зарегистрируйтесь!!!

Учимся создавать игру-головоломку с использованием AS3 и OOP

Перейти вниз

Учимся создавать игру-головоломку с использованием AS3 и OOP

Сообщение автор Admin в Вс Фев 21, 2010 9:04 pm

Идея, лежащая в основе логики игры проста. При щелчке по одной кнопке будет проверяться, имеется ли свободное место рядом с ней, и если имеется, то она будет перемещена на это свободное место. Это выглядит так, будто там есть свободное место, но на самом деле существует обычный квадрат того же класса, что и все квадраты, но другого цвета и не участвующий во взаимодействии. Переключение не имеет анимации, но так как квадраты расположены очень близко, все выглядит так, будто они перемещаются. После того, как все квадраты окажутся на месте, таймер остановится.

В данной игре у нас существует 4 объекта (4 класса):

Main class (главный)
Matrix (матрица)
Logic (логика)
Box (бокс)

Класс Main
Класс Main отвечает за следующие задачи:


создает экземпляр класса Matrix, проходя по его параметрам;
создает экземпляр класса Logic, проводя его через массив Matrix и WhiteBox;
делает AddChild класса Matrix;
создает и управляет textField и таймером.
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
/**
* main application. calls the matrix and send rows & columns
* creates logic. set 'start' button and set timer & textfield;
* @author Eitan Avgil
*
*/
public class Main extends Sprite
{
private var _matrix:Matrix;
private var _tf:TextField;
private var _logic:Logic;
private var _timer:Timer;
public function Main() {
_matrix = new Matrix(3,5);
_matrix.x = 60;
_matrix.y = 80;
_tf = new TextField();
_tf.border = true;
_tf.height = 20;
_tf.x = 10;
_tf.y = 10;
_tf.width = 150;
_tf.selectable = false;

var startButton:Sprite = new Sprite();
startButton.graphics.beginFill(0xCCCCCC)
startButton.graphics.drawRect(0,0,50,20)
var tf:TextField = new TextField()
tf.text = "start";
tf.selectable = false;
startButton.addChild(tf);
startButton.x = 50;
startButton.y = 50;
addChild(startButton)

startButton.addEventListener(MouseEvent.CLICK,onClick);

}
/**
* start the game by clicking the start text
* @param evt
*
*/
private function onClick(evt:MouseEvent):void{
removeChild(evt.currentTarget as Sprite);
addChild(_matrix);
addChild(_tf);
_logic = new Logic(_matrix.boxesArray,_matrix.whiteBox);
_logic.addEventListener(Event.COMPLETE,onGameEnd);

_timer = new Timer(10);
_timer.addEventListener(TimerEvent.TIMER,onTimerTic);
_timer.start()
}
/**
* end game text
* @param evt
*
*/
private function onGameEnd(evt:Event):void{
_timer.stop();
_tf.text = "DONE >> "+numberToTime(_timer.currentCount)
}

/**
* timer counter
* @param evt
*
*/
private function onTimerTic(evt:TimerEvent):void{
_tf.text = numberToTime(Number(Timer(evt.target).currentCount));
}

/**
* rounds the timer to 2 decimal after point
* @param n
* @return
*
*/
private function numberToTime(n:uint):String{
return String(n/100);
}
}
}

Класс Box
Этот класс представляет один квадрат. Класс получает код цвета в качестве параметра в своем конструкторе и должен нарисовать квадрат данного цвета. После того, как по квадрату щелкнули, экземпляр класса посылает событие. Класс, управляющий всеми боксами, перехватит это событие, и будет знать, что с ним делать. С некоторыми боксами нельзя взаимодействовать (белый бокс и боксы в левой колонке), поэтому они подвергнутся специальной обработке – будет отсутствовать событие по щелчку. Если мы превратим это в совершенную ООР модель, данные экземпляры станут расширениями класса Box, но я решил, что этот проект покажет, как разбить игру на классы и объекты, и не хотел добавлять слишком много ООР. Каждый бокс получает (через функцию установщик) свою первоначальную ycoordinate (координату у), поэтому он «сможет», при запросе, сообщить нам, находится ли он в правильном, или неправильном, ряду. Существует общедоступные методы квадрата:



Constructor – получает код цвета, прозрачность и толщину границы;
set heightInit – для установки первоначальной Y координаты;
setEmptyBox – для установки белого бокса;
disableInteractive – если этот бокс не взаимодействует, то удалить листенер события мыши;
isInOriginalHeight – самопроверка боксом, находится ли он в правильном ряду (возвращает true/false)
Класс также содержит статичную константу ширины квадрата, а также статичное имя события.

package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
/**
* a single square. has no logic. dispatches click event when clicked - thats it.
* @author Eitan Avgil
*
*/
public class Box extends Sprite {

// this height value is here so the box will "know" if it is in the right row
private var _originalHeight:Number;
private var _colorCode:uint;
private var _borderAlpha:Number;
private var _borderWidth:Number;
//color array - matches code to a constant color code
private const COLOR_ARRAY:Array = [0xFF0000,
0x00FF00,
0x0000FF,
0xDD00DD,
0X00FFFF,
0xFFFF00];
// defenition of box size
public static const BOX_SIZE:uint = 20;
//clicked string event name
public static const BOX_CLICKED:String = "clicked";

//C'tor
public function Box(colorCode:uint,borderAlpha:Number=0,borderWidth:Number=0){
_colorCode = colorCode;
_borderAlpha = borderAlpha;
_borderWidth = borderWidth;
drawRect(COLOR_ARRAY[_colorCode]);
addEventListener(MouseEvent.CLICK,onMouseClick)
}
// just draw graphic rectangle
private function drawRect(color:uint):void{
graphics.beginFill(color);
graphics.lineStyle(_borderWidth,0xFFFFFF,_borderAlpha)
graphics.drawRect(0,0,BOX_SIZE,BOX_SIZE)
graphics.endFill();
}
//inner click listening
private function onMouseClick(evt:MouseEvent):void{
// dispatches event to whome it may listen
//trace(Box(evt.target).colorCode)
dispatchEvent(new Event(BOX_CLICKED));
}

// remember the initialized Y coordinate for checking later on.
public function set heightInit(value:Number):void{
_originalHeight = value;
}

// the whiteBox
public function setEmptyBox():void{
graphics.clear();
_colorCode = undefined;
drawRect(0xFFFFFF);
alpha=0.2;
}
//remove interactive for the non-interactive boxes
public function disableInteractive():void{
removeEventListener(MouseEvent.CLICK,onMouseClick);
}
// check if current instance is in it's initialize (correct) Y coordinate.
public function isInOriginalHeight():Boolean{
if(this.y==_originalHeight) return true;
return false
}
}
}

Класс Matrix
Этот класс имеет в игре единственный экземпляр, и он создает и размещает все боксы. В его обязанности входит перемешать все квадраты (только взаимодействующие квадраты) и позволить другим классам получить массив всех боксов: создать, организовать смешивание и собрать все боксы в массив. Вот и все.

Мы также могли бы написать логическую часть игры внутри класса, но мы хотели показать, как разделить создание и логику, поэтому решили сделать два отдельных класса. Класс Matrix имеет следующие общедоступные методы:



Constructor – получает номера рядов и колонок;

get boxesArray – возвращает массив всех взаимодействующих боксов;

get whiteBox – возвращает экземпляр белого бокса

Хотя функция перестановки является частным методом, мы хотели бы сказать несколько слов о ней. Основная идея перестановки здесь заключается в замене координат объектов. Никакие команды массива и никакие двумерные массивы сюда не включаются. Так как это делается?

Когда мы создаем боксы, мы помещаем их в массив (простой одномерный массив) в порядке, в котором их создаем. Функция перестановки пробегает по массиву, выбирает экземпляр текущего индекса и просто заменяет его координаты координатами другого случайного бокса из массива. Так происходит замена каждого бокса, по крайней мере, один раз. Все боксы находятся в массиве, но со смешанными координатами.
package
{
import flash.display.Sprite;
import flash.geom.Point;
/**
* a visual class - places boxes by rows & columns
* places the empty box
* scramble all boxes
* @author Eitan Avgil
*/
public class Matrix extends Sprite{

private var _whiteBox:Box
private var _rows:uint;
private var _cols:uint;
private var _gap:uint; // space between the boxes
private var _boxes:Array; // 2 dimentions array of organized colored boxes
public static const GAP:uint = 5;

public function Matrix(rows:uint,cols:uint){
_rows = rows;
_cols = cols;
_boxes = new Array();
buildMatrix();
buildGuideBoxes();
addWhiteBox();
scramble();
}
// build the matrix. and pushes all boxes to an array
private function buildMatrix():void{
var i:uint;
var j:uint;
var box:Box;
for(i=0;i< _rows;i++){
for(j=0;j< _cols;j++){
// create a new box and push it to the row
box = new Box(i);
addChild(box);
box.x = j*(Box.BOX_SIZE+GAP);
box.y = i*(Box.BOX_SIZE+GAP);
box.heightInit = box.y;
_boxes.push(box);
}
}
}
// build the left collumn boxes
private function buildGuideBoxes():void{
var j:uint;
var box:Box;
for(j=0;j< _rows;j++){
box = new Box(j,1,2);
box.disableInteractive();
addChild(box);
box.x = -Box.BOX_SIZE-(GAP*3);
box.y = j*(Box.BOX_SIZE+GAP);
}
}
// creates the non-colored box
private function addWhiteBox():void{
_whiteBox = new Box(0);
_whiteBox.setEmptyBox();
_whiteBox.disableInteractive();
addChild(_whiteBox);
_whiteBox.y = -Box.BOX_SIZE-GAP;
_whiteBox.heightInit = _whiteBox.y;
}

// scramble all boxes. make sure whitebox and first box is nor scrambled
private function scramble():void{
var point:Point
var box:Box;
var tmpBox:Box
var rnd:uint;
var max:uint = _boxes.length-1;
for (var i:uint=1;i< max;i++){
box = _boxes[i];
point = new Point(box.x,box.y);
rnd=(Math.random()*max)+1;
tmpBox = _boxes[rnd];
switchPlaces(box,tmpBox);
}
}

// switches places between 2 boxes
private function switchPlaces(box:Box,box2:Box):void{
var point:Point = new Point(box.x,box.y);
box.x = box2.x;
box.y = box2.y;
box2.x = point.x;
box2.y = point.y;
}

public function get boxesArray():Array{
return _boxes;
}

public function get whiteBox():Box{
return _whiteBox;
}
}
}



Класс Logic
Этот класс является мозгом игры. Он получает массив (массив, созданный классом matrix) и просто слушает события, исходящие от его элементов. После добавления листенеров событий он сидит и ждет, пока один из боксов не пошлет событие «По мне щелкнули».

Когда класс Logic получает это событие, он проверяет, является ли бокс, пославший событие, боксом, расположенным следом за белым боксом, путем проверки его координат. Он проверяет, одинакова ли у них (щелкнутого бокса и белого бокса) координата Y, если да, то равно ли горизонтальное смещение между ними ширине одного бокса (плюс промежуток); то же самое имеет отношение и к другому измерению.

Если, и только если, щелкнутый бокс находится за белым боксом, мы меняем их координаты. После их замены, класс Logic проверяет, находится ли белый бокс в своем первоначальном месте (за пределами цветных рядов) и если да, он проверяет все позиции боксов. Мы разделили эту проверку на две части, потому что при каждой замене нам не нужно проверять, находятся ли все боксы на месте. Игра может закончиться, только когда белый бокс находится на своем месте, поэтому таким способом мы можем избежать ненужных проверок.

Проверка всех боксов проста. Поскольку мы наделили каждый бокс возможностью «знать», находится ли он в нужной координате Y, все, что нам нужно сделать, это запустить простой цикл for и просто спрашивать "с тобой все в порядке?". Если ни один бокс не будет знать, расположен ли он корректно, то проверка немного усложнится (какого ты цвета? каково твое положение по Y? верна ли эта позиция для данного цвета?). Игра, более грамотно разработанная средствами объектно-ориентированного программирования, отделит вид от данных, поэтому проверка просто будет происходить на стороне данных, но это слишком объектно-ориентировано для этой небольшой игры.

package
{
import flash.events.EventDispatcher;
import flash.events.Event;
import flash.geom.Point;

public class Logic extends EventDispatcher{

private var _boxes:Array;
private var _whiteBox:Box;

//C'tor
public function Logic(boxes:Array,whiteBox:Box){
_boxes = boxes;
_whiteBox = whiteBox;
addListenersToBoxes();
}

// add click listener to the interactive boxes.
private function addListenersToBoxes():void{
var box:Box;
for(var i:uint=0;i< _boxes.length;i++){
box = _boxes[i] as Box;
box.addEventListener(Box.BOX_CLICKED,onBoxClicked)
}
}

// the function that is attached to clicks on every interactive box
private function onBoxClicked(evt:Event):void{
var box:Box = evt.currentTarget as Box;
checkBox(box);
}

// checks if the given box is next to the whiteBox
private function checkBox(box:Box):void{
if((box.x==_whiteBox.x && (Math.abs(box.y-_whiteBox.y)==Box.BOX_SIZE+Matrix.GAP))
|| (box.y==_whiteBox.y && (Math.abs(box.x-_whiteBox.x)==Box.BOX_SIZE+Matrix.GAP))){
switchPlaces(box,_whiteBox);
}
}


//switches places between the white box and a given box
private function switchPlaces(box:Box,box2:Box):void{
var point:Point = new Point(box.x,box.y);
box.x = box2.x;
box.y = box2.y;
box2.x = point.x;
box2.y = point.y;
checkWhiteBox();
}

// check if the whitebox is in it's place. only if it is -
// continue checking all other boxes.
private function checkWhiteBox():void{
if(_whiteBox.isInOriginalHeight()){
//trace("whitebox in place - let's check all other boxes")
checkAllBoxes();
}
}


// ask each box if it is in it's original height
private function checkAllBoxes():void{
for (var i:uint=0;i< _boxes.length;i++){
if(!Box(_boxes[i]).isInOriginalHeight()){
//trace("sorry - not all boxes are in their places");
return;
}
}
//trace("all in place")
dispatchEvent(new Event(Event.COMPLETE));
}

}
}

Выводы

Это приложение было создано, чтобы показать некоторые принципы объектно-ориентированного программирования:



как разделить приложение на разные объекты и функции каждого объекта;
как построить визуальную матрицу и перемешать боксы;
как пройти данные (массив боксов) и отделить слой логики от визуального слоя;
как установить связь между классами: более низкий класс посылает события более высокому классу, более высокий класс использует прямой доступ через указатель экземпляра;
как статические элементы используются между классами (имена событий, постоянные номера).
avatar
Admin
Admin

Сообщения : 92
Очки : 280
Репутация : 36
Дата регистрации : 2010-02-20

Посмотреть профиль http://game-dll.mirbb.net

Вернуться к началу Перейти вниз

Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения