|
Изучаем DelphiX. Часть 3: Крутим
спрайты.
|
Автор: Влад
Энгельгардт
"Крутимся не накрутимся:)"
В DelphiX существует маленькая проблема, это разворот
спрайтов. Она решается относительно просто, сейчас я объясню,
как и что.
Для начала создадим новый проект в Delphi, подстроим его
под шаблон (об этом написано в одной из частей цикла). Далее
скачиваем мною модернизированный DXSprite.pas (Dxsprite.rar),
в папке, где у вас установлен DelphiX есть папочка Source,
копируем его туда. В DXSprite.pas я добавил всего одну
процедуру Angle это и есть процедура разворота, как она
работает можете посмотреть, сами поковырявшись в исходниках.
Ну что приступим, для начала создадим простенький пример (в
архиве с исходниками в папке 3.1).
Создаём новый класс:
|
TPlayerone = class(TImageSprite)
protected
procedure DoMove(MoveCount: Integer); override;
end;
|
|
Соответственно и процедуру DoMove к этому классу:
|
Procedure TPlayerone.DoMove(MoveCount: Integer);
begin
inherited DoMove(MoveCount);
x:=x+cos256(Angle)*speed; //обработчик движения по X
y:=y+sin256(Angle)*speed; //обработчик движения по Y
if y >= form1.DXDraw1.SurfaceHeight-image.Height then
y := form1.DXDraw1.SurfaceHeight-image.Height;
if x >= form1.DXDraw1.SurfaceWidth -image.Width then
x := form1.DXDraw1.SurfaceWidth -image.Width;
if y <= 0 then
y := 1;
if x <= 0 then
x:=1;
begin
speed:=0; //когда ничего не делаем, скорость равна 0
if isLeft in Form1.DXInput1.States then angle:=angle-5;
if isRight in Form1.DXInput1.States then angle:=angle+5;
if isup in Form1.DXInput1.States then speed:=4;
if isDown in Form1.DXInput1.States then speed:=-4;
end;
end;
|
|
И перед implementation в Var добавляем:
|
var
Form1: TForm1;
speed: integer; // Это у нас переменная скорости объекта
|
|
Не забудь в DXtimer добавить:
|
procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);
begin
if not DXDraw1.CanDraw then exit;
DXInput1.Update;
DXSpriteEngine1.Move(LagCount);
DXSpriteEngine1.Dead;
DXDraw1.Surface.Fill(0);
DXSpriteEngine1.Draw;
DXDraw1.Flip;
end;
|
|
И в процедуре FormCreate создаём наш спрайт:
|
procedure TForm1.FormCreate(Sender: TObject);
begin
with TPlayerone.Create(Dxspriteengine1.Engine) do
begin
PixelCheck := True;
Image := form1.dxImageList1.Items.Find('krut');
x:=350;
y:=250;
Width := Image.Width;
Height := Image.Height;
end;
end;
|
|
Далее в DXImageList добавляем спрайт "krut" он у меня
выглядит вот так:
И всё, простейший пример с Angle готов.
Теперь давайте усложним наш пример и сделаем так что бы
игрок стрелял в ту сторону куда он смотрит, для этого создаём
новый класс для патрона. Выглядит он так:
|
TPlayerFa = class(TImageSprite)
protected
procedure DoMove(MoveCount: Integer); override;
private
anglefa:integer; // Угол под которым летит пуля
public
constructor Create(AParent: TSprite); override;
destructor Destroy; override;
end;
|
|
Перед implementation добавляется ещё одна переменная ang:
|
var
Form1: TForm1;
speed,ang: integer;
|
|
Она нужна для выноса значения Angle плеера. А зачем нужен
вынос, спросишь ты, да всё очень просто, он нужен для патрона,
чтобы указывать ему, под каким углом лететь т.е. под каким
углом находиться плеер под таким углом и летит патрон.
Маленько модернизируем класс плеера:
|
TPlayerone = class(TImageSprite)
private
lngpolet:integer; // мы же не хотим, чтобы наши пули летали кучами
oldlngpolet:integer; // а мы сделаем чтобы летали стаями :)
protected
procedure DoMove(MoveCount: Integer); override;
end;
|
|
Сразу, чтобы не забыть конструктор и деструктор для
патрона:
|
constructor TPlayerFa.Create(AParent: TSprite);
begin
inherited Create(AParent);
Image := form1.DXImageList1.Items.Find('pul');
Width := Image.Width;
Height := Image.Height;
end;
destructor TPlayerFa.Destroy;
begin
inherited Destroy;
end;
|
|
Ну а теперь движение патрона:
|
procedure TPlayerFa.DoMove(MoveCount: Integer);
begin
inherited DoMove(MoveCount);
angle := anglefa;
x:=x+cos256(angle)*7; // цифра 7 здесь скорость патрона и изменять её надо
y:=y+sin256(angle)*7; // пропорционально
if X>= 800 then Dead;
if y>= 600 then Dead;
if X<= 0 then Dead;
if y<= 0 then Dead;
Collision;
end;
|
|
Надеюсь, ты помнишь, на прошлых уроках я рассказывал тебе
об DXInput, так вот теперь нам понадобятся дополнительные
кнопочки. Их значения ты можешь поменять, два раза кликнув на
самом компоненте. Так вот, гляди процедуру DoMove у игрока:
|
Procedure TPlayerone.DoMove(MoveCount: Integer);
begin
inherited DoMove(MoveCount);
ang:=angle; // наша переменная для патрона
x:=x+cos256(Angle)*speed;
y:=y+sin256(Angle)*speed;
if y >= form1.DXDraw1.SurfaceHeight-image.Height then
y := form1.DXDraw1.SurfaceHeight-image.Height;
if x >= form1.DXDraw1.SurfaceWidth -image.Width then
x := form1.DXDraw1.SurfaceWidth -image.Width;
if y <= 0 then
y := 1;
if x <= 0 then
x:=1;
begin
speed:=0;
if isLeft in Form1.DXInput1.States then angle:=angle-5;
if isRight in Form1.DXInput1.States then angle:=angle+5;
if isup in Form1.DXInput1.States then speed:=4;
if isDown in Form1.DXInput1.States then speed:=-4;
if isbutton1 in Form1.DXInput1.States then
begin
if lngpolet-oldlngpolet>=250 then
begin
Inc(lngpolet);
with TPlayerFa.Create(Engine) do
begin
Image := form1.DXImageList1.Items.Find('pul');
X:=self.X+cos256(ang)*50; // здесь 50 расстояние патрона от плеера
Y:=self.y+sin256(ang)*50; //они должны быть пропорциональны
anglefa:=ang; // передаём угол
oldlngpolet := lngpolet;
end;
end;
end;
lngpolet := lngpolet + MoveCount;
end;
end;
|
|
И последнее что осталось это создать спрайт "pul" в
DXimageList. Он выглядит у меня вот так:
Что, устал? Ну, расслабься, если хочешь. Дальше мы сделаем
следующее: 1. Добавим второго плеера 2. И научимся
делать стрэйфы
Для начала установим новую раскладку в Dxinput. Для этого
два раза кликнем по кампоненту Dxinput и преходим в закладку
Keybord:
Сразу маленький совет: чтобы на одной кнопке у тебя не было
300 действий для каждой метки, назначай только одну кнопку.
Я выбрал такой вариант для первого игрока: Up- Num 8
Down - Num 5 Left - Num 4 Right - Num 6
Button1 - Num 0 Button2 - Num 7 Button3 - Num 9
Для второго игрока: Button4 - T Button5 -G
Button6 -F Button7- H Button8 -E Button9 -R
Button10 -Y
Посмотрите на клаву и поймёте раскладку. Ну что,
приступаем, для начала перед Implementation добавим ещё две
переменные, только уже для второго игрока:
|
var
Form1: TForm1;
speed,speed2,ang,ang2: integer;
implementation
|
|
Теперь соответственно добавляем 2 игрока и модернизируем
первого:
|
TPlayerone = class(TImageSprite)
private
lngpolet:integer;
oldlngpolet:integer;
protected
procedure DoMove(MoveCount: Integer); override;
//Добавили столкновение
procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;
end;
TPlayertwo = class(TImageSprite)
private
lngpolet:integer;
oldlngpolet:integer;
protected
procedure DoMove(MoveCount: Integer); override;
procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;
end;
|
|
и теперь обрабатываем DoCollision у обоих плееров:
|
procedure TPlayerone.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
if Sprite is Tplayerfa then dead;
end;
procedure TPlayertwo.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
if Sprite is Tplayerfa then dead;
end;
|
|
Процедура DoMove у первого игрока в корне изменяется:
|
Procedure tPlayerone.DoMove(MoveCount: Integer);
begin
inherited DoMove(MoveCount);
ang:=angle;
x:=x+cos256(Angle)*speed;
y:=y+sin256(Angle)*speed;
if y >= form1.DXDraw1.SurfaceHeight-image.Height then
y := form1.DXDraw1.SurfaceHeight-image.Height;
if x >= form1.DXDraw1.SurfaceWidth -image.Width then
x := form1.DXDraw1.SurfaceWidth -image.Width;
if y <= 0 then
y := 1;
if x <= 0 then
x:=1;
begin
speed:=0;
if isLeft in Form1.DXInput1.States then angle:=angle-5;
if isRight in Form1.DXInput1.States then angle:=angle+5;
if isup in Form1.DXInput1.States then speed:=4;
if isDown in Form1.DXInput1.States then speed:=-4;
if isbutton2 in Form1.DXInput1.States then
begin
x:= x+cos256 (angle+64)*3; // это у нас стрейф
y:= y+sin256 (angle+64)*3; // 3 - на сколько быстро стрейфиться
end;
if isbutton3 in Form1.DXInput1.States then
begin
x:= x+cos256 (angle-64)*3; //тоже стрейф, только в
y:= y+sin256 (angle-64)*3; //другую сторону
end;
if isbutton1 in Form1.DXInput1.States then
begin
if lngpolet-oldlngpolet>=250 then
begin
Inc(lngpolet);
with TPlayerFa.Create(Engine) do
begin
Image := form1.DXImageList1.Items.Find('pul');
X:=self.X+cos256(ang)*50;
Y:=self.y+sin256(ang)*50;
anglefa:=ang;
oldlngpolet := lngpolet;
end;
end;
end;
lngpolet := lngpolet + MoveCount;
end;
Collision;
end;
|
|
Аналогично и у второго плеера:
|
Procedure TPlayertwo.DoMove(MoveCount: Integer);
begin
inherited DoMove(MoveCount);
ang2:=angle;
x:=x+cos256(Angle)*speed2;
y:=y+sin256(Angle)*speed2;
if y >= form1.DXDraw1.SurfaceHeight-image.Height then
y := form1.DXDraw1.SurfaceHeight-image.Height;
if x >= form1.DXDraw1.SurfaceWidth -image.Width then
x := form1.DXDraw1.SurfaceWidth -image.Width;
if y <= 0 then
y := 1;
if x <= 0 then
x:=1;
begin
speed2:=0;
if isbutton6 in Form1.DXInput1.States then angle:=angle-5;
if isbutton7 in Form1.DXInput1.States then angle:=angle+5;
if isbutton4 in Form1.DXInput1.States then speed2:=4;
if isbutton5 in Form1.DXInput1.States then speed2:=-4;
if isbutton9 in Form1.DXInput1.States then
begin
x:= x+cos256 (angle-64)*3;
y:= y+sin256 (angle-64)*3;
end;
if isbutton10 in Form1.DXInput1.States then
begin
x:= x+cos256 (angle+64)*3;
y:= y+sin256 (angle+64)*3;
end;
if isbutton8 in Form1.DXInput1.States then
begin
if lngpolet-oldlngpolet>=250 then
begin
Inc(lngpolet);
with TPlayerFa.Create(Engine) do
begin
Image := form1.DXImageList1.Items.Find('pul');
X:=self.X+cos256(ang2)*50;
Y:=self.y+sin256(ang2)*50;
anglefa:=ang2;
oldlngpolet := lngpolet;
end;
end;
end;
lngpolet := lngpolet + MoveCount;
end;
Collision;
end;
|
|
И последнее в FormCreate добавляем, чтобы второй игрок
креатился:
|
procedure TForm1.FormCreate(Sender: TObject);
begin
with TPlayerone.Create(Dxspriteengine1.Engine) do
begin
PixelCheck := True;
Image := form1.dxImageList1.Items.Find('krut');
x:=350;
y:=250;
Width := Image.Width;
Height := Image.Height;
end;
with TPlayertwo.Create(Dxspriteengine1.Engine) do
begin
PixelCheck := True;
Image := form1.dxImageList1.Items.Find('krut');
x:=50;
y:=250;
Width := Image.Width;
Height := Image.Height;
end;
|
|
И пару советов, для дебага можно выводить любую переменную
на экран. Для этого в DXTimer добавим следующие строчки:
|
procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);
begin
if not DXDraw1.CanDraw then exit;
DXInput1.Update;
DXSpriteEngine1.Move(LagCount);
DXSpriteEngine1.Dead;
DXDraw1.Surface.Fill(0);
DXSpriteEngine1.Draw;
with DXDraw1.Surface.Canvas do
begin
Brush.Style := bsClear; //стиль
Font.Color := clWhite; //цвет текста
Font.Size := 12; // размер
Textout(0, 0, 'FPS: '+inttostr(DXTimer1.FrameRate)); //вывод текста
Textout(0, 24, 'спрайты: '+inttostr(DXSpriteEngine1.Engine.AllCount));
Release;
end;
DXDraw1.Flip;
end;
|
|
Здесь я вывожу Fps (кадры в секунду), и количество спрайтов
на экране.
Д/З: 1. Сделай анимированные патроны. 2. Реализуй,
чтобы вторым игроком управлял не человек, а созданный тобой
интеллект. 3. Сделай так, чтобы вёлся счёт фрагов. 4.
После смерти любого игрока, чтобы через 5 сек. происходил
респаун.
Если у вас возникли какие-то вопросы или проблемы, пишите
мне.
Автор: Влад
Энгельгардт
Cтатьи цикла:
Изучаем
DelphiX. Часть 0: Теория. Изучаем
DelphiX. Часть 1: Первая игра. Изучаем
DelphiX. Часть 2: Усложняем нашу игру. Изучаем
DelphiX. Часть 3: Крутим спрайты. Изучаем
DelphiX. Часть 4: Разбираем классы. Изучаем
DelphiX. Часть 5: AI.
|