Всем привет. Подскажите пожалуйста. Кто ни будь делал игру на подобии BrickShooter?
Как вообще там все строится? Мне кажется я не правильно отрисовал игровое поле. Так как если один тайл уже в движении, то щелкнув по другому - первый останавливается. И если первоначальные координаты тайла изменилились, а на его место передвинулся очередной тайл. Как запустить очередной тайл?
У меня в рекорд записываются координаты X,Y(нужно или нет?), Check- если тайл не дошел до финиша- для того что бы когда исчезнет преграда он продолжил движение, Enabled -если тайл удален, ну и pic- номер рисунка.
type
TTile = record
X, Y: integer;
Check, Enabled: Boolean;
pic : byte;
end;
var
Tile : array [0..20,0..20] of TTile;
M : TPoint;
TX, TY, selx, sely, openx, openy : integer;
///////////////
procedure TMainForm.ShapeMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) then begin
openx := selx;
openy := sely;
procedure TMainForm.ProcessEvent(Sender: TObject);
begin
GetCursorPos(M);
TX := M.x - MainForm.left-3;
TY := M.y - MainForm.top-26;
selx := (TX-15) div 30-5; // X:= x-1;
sely := (TY-26) div 30;
with Tile[openX,openY] do begin
if (X>=230)then
X:= X-5;
end;
with Tile[openX+1,openY] do begin
if X>=710 then
X:= X-5;
end;
with Tile[openX+2,openY] do begin
if X>=740 then
X:= X-5;
end;
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
07-06-2019 22:55
>>> Не соображу как удалить те которые вышли за предел или как сделать что бы самоуничтожились.
Свои координаты кубик меняет в Process**** (либо Motion, либо Event), так что именно там (если вы брали за основу мой код) надо выставить влажок Dead:=true. "Сборщик мусора" удалит такие кубики.
>>> не могу кликнуть по кубику который перешел на другую сторону. Нужно как то перерисовать все поле? Или переписать Bricks с изменившимися кубиками?
Не совсем понял, что значит "перешёл на другую сторону"? Это значит, что кубик, выдвинувшийся справа, должен "телепортироваться" налево? Не вижу проблемы. При такой "телепортации" вы в любом случае пересчитываете координаты, так что клик в любом случае будет проходить. Полагаю, у вас ошибка в пересчёте координат в ProcessMotion, которая просчитывает эту "телепортацию". Или вы что-то другое имеете в виду?
то Python, спасибо за наставления. Изменил и добавил функции. Кубики бегают по полю.
1)Не соображу как удалить те которые вышли за предел или как сделать что бы самоуничтожились.
2)не могу кликнуть по кубику который перешел на другую сторону. Нужно как то перерисовать все поле? Или переписать Bricks с изменившимися кубиками?
У меня на движке Asphyre, но для примера переписал Ваш код, стал чуть длиннее, но так понятней что и как. Могу выложить если нужно.
Ах да, если у вас стен не будет в принципе, то можно обойтись и без обратного хода волны. Просто при попытке (чем она вызвана, кстати?) передвижения кубика он будет отправлять сообщение соседу справа "Сдвигайся" и всё. Нужно будет, правда, ещё добавить код, который проверяет, что кубик вышел за пределы игрового поля (согласно моей идеологии, это должно проверяться в GameSystem, но в принипе, можно и чтобы кубик сам ликвидировался при пересечении поля), чтобы не засорять умершими кубиками линейный список.
>>> Подскажите, если использовать Ваш код, то для поиска (соседей) нужно будет добавлять и (function FindNeighbourRight(Brick:TBrick):TBrick;)
Да, именно так (если потребуется работа с соседями справа и слева). Возможно, имеет смысл сделать одну процедуру вида FindNeighbour(NeighbourType:(ntLeft,ntTop,ntRight,ntBottom)):TBrick; псевдокод, не пытайтесь скомпилировать, чтобы избежать дублирования кода при проверке координаты X на ntTop, ntBottom, но это необязательно.
>>> когда кубик пройдет все игровое поле и столкнувшись с параллельным кубиком, он его сдвинул, а не остановился перед ним
Для этого у меня в сообщении есть поля _source и _target. Обратите внимание, _source в моём коде нафиг не нужен, может показаться, что он лишний. Но это не так - в вашем случае он пригодится. Назовём это "обратным ходом волны". Принцип будет следующий: кубик А отправляет своему соседу справа Б сообщение и, если Б может двигаться (не упёрся в стену, например, если у вас стены будут), то Б сдвигается на свободное место и отправляет обратно А сообщение: "Сдвигайся, свободно!". Соответственно, если А пытался сдвинуться как запрос от кубика Г, то он отправит обратно Г аналогичное сообщение "Сдвигайся, свободно!" и так пока не закончится обратный ход волны.
Также на обратном ходе, возможно, имеет смысл проверить, не появились ли пустые места, которые надо заполнить. И если да, то "родить" новых кубиков.
PS: Кстати, предложенный вариант неоптимальный. Каждый поиск бегает по всему линейному списку и это плохо. Было бы лучше сделать некий "кеш соседей" для каждого кубика, потому что скорее всего соседи меняться не будут, а потому, можно будет быть уверенным, что если у кубика Ф был сосед сверху Д, то и через ход, если Д жив, то у Ф по прежнему сосед сверху будет Д. Впрочем, на мелком игровом поле тормоза незаметны.
To Python: Подскажите, если использовать Ваш код, то для поиска (соседей) нужно будет добавлять и (function FindNeighbourRight(Brick:TBrick):TBrick;) и (function FindNeighbourLeft(Brick:TBrick):TBrick;)?
Ну и еще конечно же (function IsNeighbourRight(Brick2:TBrick):boolean;) и
(function IsNeighbourLeft(Brick2:TBrick):boolean;). А как сделать что бы- когда кубик пройдет все игровое поле и столкнувшись с параллельным кубиком, он его сдвинул, а не остановился перед ним?
to Den Sarych .Если вы про пример Python'а Нет я имел в виду тот пример (код в начале вопроса). Там ведь двухмерный массив (Tile : array [0..20,0..20] of TTile;) И клик производится по квадратику (Tile[openX,openY]).
23-05-2019 06:59 | Комментарий к предыдущим ответам
>>>Они передвигаются и создаются, но не получается в двухмерном массиве запустить движение (очередью) и кликнуть по второму когда первый сменил координаты.
Если вы про пример Python'а, то:
- там нет двумерного массива, там есть список квадратиков (одномерный массив Bricks :TList).
- клик производится не по квадратику, а по "игровому полю" (pb :TPaintBox), после чего перебирается целиком весь "список квадратиков", с выяснением, на какой из них пришлись координаты клика (procedure pbMouseDown).
- обработка клика и движение квадратиков - не связаны:
- раз в 1/4 секунды срабатывает таймер (procedure Timer). В этой процедуре, перебирается весь "список квадратиков" и каждый квадратик смещается на 2 по Y (падает на 2 пикселя за кадр), после чего "игровое поле" перерисовывается.
- клик игрока происходит где-то между кадров (срабатываниями таймера). При обработке этого клика некоторые квадратики изменяются (Dead := true и удаляются из списка), так что до следующей перерисовки они не доживают.
To Ник:
>>> Может быть мне не стоит использовать record
Непринципиально. Просто мне было проще использовать уже готовый TObjectList, чтобы он взял на себя управление временем жизни клеток (TBrick в терминах моей программы).
>>> но не получается в двухмерном массиве запустить движение
Использованная мною очередь сообщений - это один из вариантов, её можно реализовать и другими способами. В том числе и рекурсией (классический волновой алгоритм - вообще рекурсия).
To Den Sarych:
>>> что бы нечто подобное, самостоятельно написал автор вопроса
Понятное дело, но обычно имеем то, что имеем. И в данном случае, автор сам пишет, что в основном пытаюсь что либо сделать изучая исходники.
Den Sarych Я еще не дошел до алгоритма поиска соседей. Я всего лишь отрисовал поле и пытаюсь заставить кубики передвигатся как положено (ушел один - на его место передвинулся второй, на место второго третий, а вместо третьего создается очередной). Они передвигаются и создаются, но не получается в двухмерном массиве запустить движение (очередью) и кликнуть по второму когда первый сменил координаты.
Python , спасибо, круто получилось. Может быть мне не стоит использовать record? Den Sarych Я извиняюсь что не очень силен в программировании и в основном пытаюсь что либо сделать изучая исходники, находя в них что то полезное для себя. Понимаю что это не совсем правильно, но на хобби не достаточно времени. Ну как то вот так. И Вам тоже большое спасибо за наставления.
20-05-2019 23:11 | Комментарий к предыдущим ответам
>>>Я тут набросал примерчик...
Пример симпатичный, отлично иллюстрирует эхотаг.
Но, было желание, что бы нечто подобное, самостоятельно написал автор вопроса. Поэтому я предлагал "проектирование сверху", не от "кадров" и "алгоритма поиска соседей", а от общей компоновки программы, от "задумки логики самой игры".
Нисколько не лучше вашего примера, но возможно нагляднее для автора вопроса, было бы создание именно клеточных игр (со стационарными клетками). В них обычно создаются массивы целых чисел по размеру поля и алгоритм "логики игры" переводит один массив в другой (пример, те же "крестики-нолики", "жизнь", и т.д.).
В вашей программе, состояние одно, это одномерный массив Bricks объектов (текущее сотояние квадратиков Bricks[].Point, Color, Dead + "скорость падения" квадратиков dy=2 pix/frame). "Новое" состояние выстраивается в "старом", изменяя предыдущие значения, ровно в промежутке между кадрами. Здесь (исходя из правил именно этой игры) достаточно одного состояния.
Ваш пример отражает классический подход, основанный на конвейере обработки кадров.
Я же предлагаю идти "от задумки игры". Практически любая компьютерная "клеточная игра" была создана на основе обычной "картонной". А в "картонном" варианте игры, есть только сюжет и "логика игры". Здесь нельзя отталкиваться от "алгоритма поиска соседей квадратика" или от "красивой анимации столкновений".
Вот этот то "уровень логики игры", отражающий сюжет и правила игры, я и предлагаю разрабатывать (и если можно обособить) в первую очередь. Особенно в целях начального обучения. Чем такой подход, на практике, отличается от классического? Да практически, программный код неотличим. Но, в первом случае вы мыслите категориями сюжета игры, а уже потом перекладываете её на конвейер обработки кадров (уровень механики изображения). Во втором - наоборот.
19-05-2019 13:25 | Комментарий к предыдущим ответам
>>> Не "клетки могут принимать сообщения", а уровень "взаимодействия с игроком"
В любом случае, принимать сообщения будет приложение, которое должно переводить эти сообщения в понятный... назовём так... "игровой подсистеме" вид. Я тут набросал примерчик, без оптимизации, реализация неполная, но как-то работает:
type
TBrickEventType=(etDie,etMove);
TGameSystem=class;
TBrick=class;
TBrickEvent=class
_type:TBrickEventType;
_source,_target:TBrick;
Constructor Create(aSource,aTarget:TBrick;aType:TBrickEventType);
end;
TBrick=class
X,Y:integer;
Color:TColor;
Dead:boolean;
function IsInside(PointX,PointY:integer):boolean;
function IsNeighbour4(Brick2:TBrick):boolean;
function IsNeighbour8(Brick2:TBrick):boolean;
// указанный кубик является соседом снизу
function IsNeighbourDown(Brick2:TBrick):boolean;
// указанный кубик является соседом сверху
function IsNeighbourUp(Brick2:TBrick):boolean;
procedure ProcessEvent(Event:TBrickEvent;GameSystem:TGameSystem);
end;
TGameSystem=class
Events:TList;
Bricks:TList;
BottomLevel:integer;
Constructor Create;
procedure Init(MaxX,MaxY:integer);
procedure ProcessEvents;
function ProcessMotion:boolean;
function FindNeighbourUp(Brick:TBrick):TBrick;
function FindNeighbourDown(Brick:TBrick):TBrick;
function BrickHit(X,Y:integer):boolean;
Destructor Destroy;override;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
EventSystem:=TGameSystem.Create;
EventSystem.Init(pb.Width div BRICK_WIDTH+1,pb.Height div BRICK_HEIGHT+1);
SetTimer(Handle,1,250,nil);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
KillTimer(Handle,1);
EventSystem.Free;
end;
procedure TForm1.Timer(var M: TWMTimer);
begin
if M.TimerId=1 then begin
if EventSystem.ProcessMotion then
pb.Invalidate;
end;
end;
procedure TForm1.pbPaint(Sender: TObject);
var
I:integer;
Brick:TBrick;
Canvas:TCanvas;
begin
Canvas:=pb.Canvas;
Canvas.Pen.Color:=clWhite;
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(0,0,pb.Width,pb.Height);
For I:=0 to EventSystem.Bricks.Count-1 do begin
Brick:=TBrick(EventSystem.Bricks[I]);
if Brick.Dead then continue;
Canvas.Brush.Color:=Brick.Color;
Canvas.Pen.Color:=clBlack;
Canvas.Rectangle(
Brick.X-BRICK_WIDTH div 2,Brick.Y-BRICK_HEIGHT div 2,
Brick.X+BRICK_WIDTH div 2-1,Brick.Y+BRICK_HEIGHT div 2-1
);
end;
end;
procedure TForm1.pbMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if EventSystem.BrickHit(X,Y) then
pb.Invalidate;
end;
{ TBrick }
function TBrick.IsInside(PointX,PointY:integer):boolean;
begin
Result:=
(PointX>=X-BRICK_WIDTH div 2) and (PointX<=X+BRICK_WIDTH div 2) and
(PointY>=Y-BRICK_HEIGHT div 2) and (PointY<=Y+BRICK_HEIGHT div 2);
end;
function TBrick.IsNeighbour4(Brick2: TBrick): boolean;
begin
Result:=(Self<>Brick2) and
(abs(X-Brick2.X)<=BRICK_WIDTH) and
(abs(Y-Brick2.Y)<BRICK_HEIGHT);
if not Result then
Result:=(Self<>Brick2) and
(abs(X-Brick2.X)<BRICK_WIDTH) and
(abs(Y-Brick2.Y)<=BRICK_HEIGHT);
end;
function TBrick.IsNeighbour8(Brick2:TBrick):boolean;
begin
Result:=(Self<>Brick2) and
(abs(X-Brick2.X)<=BRICK_WIDTH) and
(abs(Y-Brick2.Y)<=BRICK_HEIGHT);
end;
function TBrick.IsNeighbourDown(Brick2: TBrick): boolean;
begin
Result:=(X=Brick2.X) and (Y=Brick2.Y-BRICK_HEIGHT);
end;
function TBrick.IsNeighbourUp(Brick2: TBrick): boolean;
begin
Result:=(X=Brick2.X) and (Y=Brick2.Y+BRICK_HEIGHT);
end;
procedure TBrick.ProcessEvent(Event: TBrickEvent; GameSystem: TGameSystem);
var
I:integer;
Brick:TBrick;
begin
// мёртвый кубик не идёт играть в ба-а-а-а-скетбол
if Dead then exit;
case Event._type of
etDie:begin
Dead:=true;
// пройдёмся по соседям, удаляя одноцветников
// получая это сообщение, соседи сделают то же самое
// получится что-то типа волнового алгоритма с развёрнутой рекурсией
For I:=GameSystem.Bricks.Count-1 downto 0 do begin
Brick:=TBrick(GameSystem.Bricks[I]);
if Brick.Dead then continue;
if (Brick.IsNeighbour4(Self)) and (Brick.Color=Color) then
GameSystem.Events.Add(TBrickEvent.Create(Self,Brick,etDie));
end;
end;
etMove:begin
Brick:=GameSystem.FindNeighbourUp(Self);
Inc(Y,2);
if Brick<>nil then
GameSystem.Events.Add(TBrickEvent.Create(Self,Brick,etMove));
end;
end;
end;
{ TGameSystem }
function TGameSystem.BrickHit(X, Y: integer):boolean;
var
I:integer;
Brick:TBrick;
begin
For I:=0 to Bricks.Count-1 do begin
Brick:=TBrick(Bricks[I]);
if Brick.IsInside(X,Y) then begin
Events.Add(TBrickEvent.Create(nil,Brick,etDie));
ProcessEvents;
Result:=true;
exit;
end;
end;
Result:=false;
end;
constructor TGameSystem.Create;
begin
inherited Create;
Events:=TList.Create;
Bricks:=TObjectList.Create;
end;
destructor TGameSystem.Destroy;
var
I:integer;
Ev:TBrickEvent;
begin
For I:=0 to Events.Count-1 do begin
Ev:=TBrickEvent(Events[I]);
Ev.Free;
end;
Events.Free;
Bricks.Free;
inherited;
end;
function TGameSystem.FindNeighbourDown(Brick: TBrick): TBrick;
var
I:integer;
begin
For I:=0 to Bricks.Count-1 do begin
Result:=TBrick(Bricks[I]);
if Brick.IsNeighbourDown(Result) then
exit;
end;
Result:=nil;
end;
function TGameSystem.FindNeighbourUp(Brick: TBrick): TBrick;
var
I:integer;
begin
For I:=0 to Bricks.Count-1 do begin
Result:=TBrick(Bricks[I]);
if Brick.IsNeighbourUp(Result) then
exit;
end;
Result:=nil;
end;
procedure TGameSystem.Init(MaxX, MaxY: integer);
var
I,J:integer;
Brick:TBrick;
begin
// создаём равномерно разбросанные по сетке ячейки со случайным цветом
For I:=0 to MaxX-1 do begin
For J:=0 to MaxY-1 do begin
Brick:=TBrick.Create;
Brick.X:=I*BRICK_WIDTH-BRICK_WIDTH div 2;
Brick.Y:=J*BRICK_HEIGHT-BRICK_HEIGHT div 2;
Brick.Color:=BRICK_COLORS[Random(Length(BRICK_COLORS))];
Bricks.Add(Brick);
end;
end;
BottomLevel:=(MaxY-1)*BRICK_HEIGHT-BRICK_HEIGHT div 2;
end;
procedure TGameSystem.ProcessEvents;
var
Event:TBrickEvent;
Brick:TBrick;
I:integer;
begin
while Events.Count>0 do begin
Event:=TBrickEvent(Events[0]);
Events.Delete(0);
Event._target.ProcessEvent(Event,Self);
Event.Free;
end;
For I:=Bricks.Count-1 downto 0 do begin
Brick:=TBrick(Bricks[I]);
if Brick.Dead then
Bricks.Delete(I);
end;
end;
function TGameSystem.ProcessMotion:boolean;
var
Event:TBrickEvent;
Brick:TBrick;
I:integer;
begin
For I:=Bricks.Count-1 downto 0 do begin
Brick:=TBrick(Bricks[I]);
// пропускаем кубики на нижней стороне
if Brick.Y>=BottomLevel then continue;
if FindNeighbourDown(Brick)=nil then
Events.Add(TBrickEvent.Create(nil,Brick,etMove));
end;
Result:=Events.Count>0;
while Events.Count>0 do begin
Event:=TBrickEvent(Events[0]);
Events.Delete(0);
Event._target.ProcessEvent(Event,Self);
Event.Free;
end;
end;
В моём примере игровая подсистема, игровые клетки и собственно приложения обмениваются сообщениями-"событиями". Благодаря этому, можно реализовывать достаточно простым и естественным способом правила игры (например, волновой алгоритм поиска удаляемых клеток и такой же волновой алгоритм поиска клеток, которые должны начать двигаться, потеряв "опору").
>>> никто не запрещает вам обработать клик игрока
В случае "двух режимов" - как раз и будет запрещать. Потому что предлагаемый уже DrawGrid такого физически не умеет, у него не существует клетки "между клеток".
19-05-2019 08:24 | Комментарий к предыдущим ответам
Такая программа проста и на свою разработку не потребует от вас много времени. Зато отлично проиллюстрирует работу массива "состояния игры". Только напишите сами, без копипаста (даже картинок).
Den Sarych , Я с Вами полностью согласен. Я не говорю что бы вообще отказаться от двух мерного массива и у меня изначально строится
for x:= 3 to 17 do
for y:= 0 to 3 do begin
P[x,y] := -1;
end;
for x:= 3 to 17 do
for y:=18 to 20 do begin
P[x,y] := -1;
end;
for x:= 0 to 2 do
for y:= 3 to 17 do begin
P[x,y] := -1;
end;
for x:= 18 to 20 do
for y:= 3 to 17 do begin
P[x,y] := -1;
end;
А вот как быть с анимацией? Как запустить массив клеток в двухмерном массиве пока не разобрался.
19-05-2019 05:32 | Вопрос к автору: запрос дополнительной информации
>>>Мне кажется что двухмерный массив - не вариант
Хм... . Массив нужен не столько для отрисовки, сколько для запоминания "состояния" игры.
Попробуйте на время оставить Asphyre.
Давайте на более простом примере, попытаемся выяснить ценность двумерного массива и назначение "состояния" игры?
для Python
Вы наверное и сами догадались, что формулировки "Вся игра будет находиться всего в двух режимах" и "Вариант 3 не очень интересен, ... я бы реализовал более сложный вариант игры, в котором клетки могут одновременно двигаться и принимать сообщения.", с точки зрения программирования, если обособлены уровни "логики игры", "механики отображения" и "взаимодействия с игроком", практически идентичны. Но согласитесь, насколько, более понятной для новичка и полезной для отладки, выглядит первая формулировка.
Поясню. Не "клетки могут принимать сообщения", а уровень "взаимодействия с игроком" принимает сообщения от игрока. Уровень "логики" определяет, что означает этот клик, затем вычисляет "исходное" и "целевое" состояние. А уровень "механики отображения" просто показывает изменение состояния игры.
Например, вы делаете игру "пятнашки". По клику игрока, фишка, со скоростью черепахи, начинает перемещаться. Т.е. уровень "логики" определил "исходное" (что было) и "целевое" (что будет) состояния, а уровень "механики" пытается отобразить переход из одного состояния в другое. Для отладки, полезнее досмотреть это переползание до конца. Но никто не запрещает вам обработать клик игрока и в процессе переползания фишки. В этом случае, уровень "логики" перекинет состояние игры в новое, а "механика" отобразит переползание фишки уже в обратную сторону. ;-)
Сперва хочу посоветовать статью Антона Григорьева Клетчатые игры
Ну а потом уже - свои соображения по этому поводу. Вариант 3 не очень интересен, так как выглядит некрасиво (игрок лишается возможности действовать, пока идёт анимация, это во всех играх просто вымораживает и практически всегда приводит к потере интереса к игре, знаю как по собственному опыту, так и по опыту других игроков, включая тех, что стримит игры на ютубе... конечно, не такие простые, как брикшутер, но и намного более сложные игры грешат тем же самым). Потому, я бы релизовал более сложный вариант игры, в котором клетки могут одновременно двигаться и принимать сообщения. Конечно, реализация будет существенно сложнее, зато интереснее.
Я пока сходу не смог придумать оптимального алгоритма реализации, пока в голову приходит только вариант, при котором игровое поле (клеточное) будет знать, что на одной виртуальной клетке может быть вообще говоря до четырёх физических клеток (это если допускать движение и по горизонтали и по вертикали, при движении только по вертикали - только две физические клетки), будет уметь определять, какие клетки являются соседями при любом их движении (например, если игрок кликает по движущемуся столбцу, но при этом левый соседний столбец тоже двигается, а правый - неподвижен, это не должно приводить к ошибкам) и рассылать сообщения только соседям (например, при уничтожении клетки, она должна разослать всем соседям того же цвета сообщение, чтобы они тоже уничтожились). Возможно, когда придёт в голову разумный вариант, я реализую и отпишусь тут.
Den Sarych, большое спасибо, буду пробовать. Сейчас пробую на движке Asphyre. Пытался на DelphiX соорудить при помощи спрайтов, опять же с массивом не смог разобраться (спайт ушел со своего места и капец). В Asphyre пытаюсь остановить тайл во время движения, типа
var P: array [0..50,0..50] of integer;
......................
with Tile[openX,openY] do begin
if (X>=230) and P[openX-1,openY]<> 5 then
X:= X-5;
end;
>>>Как вообще там всё строится?
Разделите разработку вашей игры на три этапа:
1. Разработка "логики игры";
2. Разработка "механики передвижений" и собственно анимации;
3. Добавление к анимации логики игры.
1. Вообще, клеточные игры, внутри программы представляются некоторым двумерным полем значений. Для простоты, будем считать его прямоугольным, с размером X на Y. Соответственно значения будут храниться в массиве A[1..x, 1..y]. Этот массив будем называть "состоянием игры" или "массивом состояний". В интерфейсе игры, отображается "игровое поле", разделённое на клетки, причём каждой клетке "игрового поля" соответствует значение "массива состояний". Например, значения могут быть такие: "пустая", "подвижная", "неподвижная", "центральная". Дополнительно, для всех, кроме "пустой", может быть указан "тип рисунка клетки".
При действиях игрока (клике на клетке), игра должна "отреагировать". Т.е. из начального состояния A (читай, значений массива) перейти в новое состояние B. Другими словами, на основании значений массива A, создаётся массив B, с внесёнными в него изменениями. После этого "игровое поле" очищается и заполняется картинками соответствующими значениям массива B.
Собственно вся логика игры и заключается в правилах перевода "состояний игры" из одного в другое, да распознавании "выигрышного состояния".
Чтобы отладить логику игры, удобнее всего написать программку-прототип, "игровое поле" в которой, будет изображать обычный DrawGrid. Здесь не нужно никакой анимации. Клик на клетке и игра перескакивает из одного состояния в другое, а "игровое поле" перерисовывается. И картинки, и "массив состояний", мы потом используем в окончательном варианте игры, но уже на этом этапе игра будет вполне играбельна.
2. "Механика передвижений" - это программирование отображения "клеток" на "игровом поле". Например, их может быть три типа: "неподвижная", "передвигающаяся", "исчезающая". На основе какой технологии (TShape, DelphiX, ...) вы будете делать перемещение квадратика по экрану не столь важно. Важно чтобы, именно "квадратики", и именно "красиво ползали" по "игровому полю" от одного фиксированного места (клетки) до другого.
Обязательно сделайте отдельный прототип этой программы. Пока без "логики игры", простое отображение и перемещение квадратика с картинкой.
3. Окончательный вариант игры, получается добавлением результата 1-го этапа ко 2-му. Вся игра будет находиться всего в двух режимах. В первом из них, ничего не будет происходить, но "игровое поле" будет ожидать действий игрока. Во втором - будет происходить анимация (как в 2) смены одного состояния игры в другое (как в 1), но "игровое поле" не будет реагировать на действия игрока.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.