Помощь - Поиск - Пользователи - Календарь
Полная версия: Шарики
Форум «Всё о Паскале» > Pascal, Object Pascal > Написание игр
Bokul
Тема зародилась Задачник по ООП, а это ее продолжения.
Вот структура того, что я написал (измененная)

1 TGObject -// движущейся графический объект, умеет:
// - инитиализировать себя
constructor init(speed,color:byte; angle,time:real);
// - двигаться
procedure moveto;
//- высчитывать свое новое положение, перекрывается в наследниках
procedure calculation; virtual; //abstract;
//- вызывается с Supervisor, только в случае столкновения.
// Меняем скорость и угол полета.
procedure ChangeDirection(speed:byte; angle:real);
// рисуем себя, перекрывается в наследниках
procedure show; virtual; // abstract;
// стираем себя, перекрывается в наследниках
procedure hide; virtual; // abstract;
// стирает себя с экрана
destructor done; virtual;

2 TBall - наследник TGObject, теперь это движущейся шарик
// - добавили новое поле r -радиус, инициализацию остальных полей наследуем
constructor init(x,y:integer; speed,color:byte; angle,time:real; r:byte);
procedure moveto;// перекрываем, наслудуем
procedure calculation; virtual;// перекрываем
procedure show; virtual; // тоже перекрываем
procedure hide; virtual; // тоже перекрываем
destructor done; virtual; // перекрываем, наслудуем


3 TItem - элемент списка указателей на объекты TBall
// инициализирует свою информационную часть
constructor constructor init(Info:TPGObject; Sled:TPItem);
destructor done;// удаляем информационную часть

4 TList - содержит список указателей на объеты типа TBall
constructor init;//-инициализирует список
function AddItem(Data:TPGObject):boolean; // добавляем новый элемент
function DeleteItem(pdel:TPItem):boolean; // удаляем элемент
destructor done;//удаляем весь список


Исходники в виде модулей для FPC - Нажмите для просмотра прикрепленного файла


Возникли затруднения в написания модуля TSupervisor, а именно с главным циклом и наследием этого объекта.
Вот, что я написал

uses objects,graphl,UnitTGObject,UnitList;
const
background=black;
ballcolor=red;


{------------------------TSupervisor------------------}
type
TPSuperVisor=^TSuperVisor;
TSuperVisor=object
GObjectsList:TList;
Rect:TRect;
constructor init(xa,ya,xb,yb:integer);
function AddGObject(p:TPGObject):boolean;
function DeleteGObject(p:TPGObject):boolean;
function ChangeDirection;
function Calculation;
function WriteResults;
procedure main;
destructor done;
end;
constructor TSuperVisor.init;
begin
GObjectsList.init;
Rect.assign(xa,ya,xb,yb);
SetColor(white);
Rectangle(xa,ya,xb,yb);
end;

function TSuperVisor.AddGObject(p:TPGObject);
begin
AddGObject:=GObjectsList.AddItem(p);
end;

function TSuperVisor.DeleteGObject(p:TPGObject):boolean;
begin
DeleteTPGObject:=GObjectsList.DeleteItem(p);
end;

procedure TSuperVisor.main;
begin
end;

destructor TSuperVisor.done;
begin
GObjectsList.done;
ClearDevice;
end;

volvo
Так... Ну, у меня после прочтения программы тоже шарики... В глазах бегают только... wacko.gif

Погоди до завтра, надо на свежую голову посмотреть... Что сразу в глаза бросилось - это реализация List-а... Я помню, что ты просил выложить мою версию, но теперь - no1.gif Будем твою дорабатывать, пока она станет функционально одинаковой с моей...
Bokul
Цитата
Так... Ну, у меня после прочтения программы тоже шарики... В глазах бегают только...

respect.gif ROFL.gif lol.gif
Цитата
Погоди до завтра, надо на свежую голову посмотреть... Что сразу в глаза бросилось - это реализация List-а... Я помню, что ты просил выложить мою версию, но теперь - no1.gif Будем твою дорабатывать, пока она станет функционально одинаковой с моей...

Спасибо! Буду ждать...
volvo
smile.gif Значит, так... По порядку:

С такой реализацией списка тоже можно жить... Неудобно, правда, но ничего - это FPC, в конце концов - перегрузишь операторы - будет смотреться получше... Теперь о том, какие огрехи и в каком порядке у тебя были замечены:
  1. function TSuperVisor.DeleteGObject(p:TPGObject):boolean;
    begin
    // DeleteGObject:=GObjectsList.DeleteItem(p);
    end;
    - несоответствие типов, копаться, чтобы исправить это я не стал, просто закомментировал вызов процедуры, т.к. не совсем понятно, почему при добавлении к списку функция AddItem получает указатель типа TPGObject, а при удалении DeleteItem требует указатель типа TPItem... blink.gif
  2. Довольно опасно - потому, что проблема не синтаксическая, а логическая - ошибку придется искать отладкой:
    constructor TBall.init(x,y:integer; speed,color:byte; angle,time:real; r:byte);
    begin
    inherited init(speed,color,angle,time);
    pos_x:=x;
    pos_y:=y;
    r:=radius; // <--- Здесь !!! Надо - наоборот: radius := r
    show;
    oldx:=pos_x;
    oldy:=pos_y;
    end;
    В твоем варианте привело к тому, что для всех создаваемых объектов - шаров радиус оставался нулевым => ничего не отчерчивалось, хотя и должно было бы...
  3. Объясни смысл введения переменных oldx, oldy, если ты все равно сначала скрываешь объект, потом его пересчитываешь, и уже с новыми координатами заново перерисовываешь? Я заменил вот эту процедуру:
    procedure TBall.hide;
    begin
    bufcolor:=GetColor;
    SetColor(GetBkColor);
    // Circle(oldx,oldy,radius);
    Circle(pos_x,pos_y,radius);
    SetColor(bufcolor);
    end;
    - теперь она работает...

    + к этому - еще кое что... Зачем ты делаешь bufcolor членом объекта? Памяти много доступной? Это тебе только кажется - ее скоро не будет хватать... Ты переменной bufcolor пользуешься локально - для временного сохранения текущего цвета отрисовки, так? Шарик твой что-то выигрывает от того, что знает, какой текущий цвет установлен? Нет... Тогда зачем ему лишняя информация? Убирай это из объекта...
  4. О скорости выполнения ты тоже, как я вижу, особенно не задумываешься, сколько у тебя будет шариков максимально? 5? 10? А если понадобится имитировать "броуновское движение" и счет пойдет на сотни? Зачем просто так занимать ресурсы и время никому не нужной работой? Я про это:
      procedure TBall.calculation;
    begin
    // Здесь ты все время переводишь градусы в радианы
    pos_x:=pos_x+round(cos(degtorad(l))*v*t);
    pos_y:=pos_y+round(sin(degtorad(l))*v*t);
    l:=ArcTanDeg(pos_y,pos_x);
    end;

    +
      function TBall.ArcTanDeg(x,y:real):real;
    begin
    // А здесь - наоборот, радианы в градусы ???
    ArcTanDeg:=radtodeg(arctan2(x,y));
    end;

    Проще (и быстрее, кстати) сразу решить, что все углы хранятся в радианах (раз уж тригонометрические функции Паскаля работают именно с радианами)... Я понимаю, что пользователю как раз удобнее задавать угол в градусах, НО ведь есть конструктор!!! Пускай он возьмет на себя эту работу:
      // Получать от вызывающего класса угол в градусах ...
    constructor TGObject.init(speed,color:byte; angle,time:real);
    begin
    v:=speed;
    col:=color;
    // Здесь - сразу переводить в радианы, и работать только в радианах
    l:=angle;
    t:=time;
    end;
    Преимущество: это делается только при инициализации класса, а следовательно - только однажды, причем, функцию перевода Grad -> Rad можно заменить константой Pi / 180
Вот та основная программа, которая у меня заработала (шарики корректно двигаются, естественно, ни о каких коллизиях - ни об отталкиваниях от стенок, ни об ударениях друг об друга - пока нет речи, это добавится позднее...)

uses
objects,crt, graph,
ball, UnitTGObject, UnitItem, UnitList;
const
background=black;
ballcolor=red;


{------------------------TSupervisor------------------}
type
TPSuperVisor=^TSuperVisor;
TSuperVisor=object
GObjectsList:TList;
Rect:TRect;
constructor init(xa,ya,xb,yb:integer);
function AddGObject(p:TPGObject):boolean;
function DeleteGObject(p:TPGObject):boolean;
function ChangeDirection: boolean;
function Calculation: boolean;
function WriteResults: boolean;
procedure main;
destructor done;
end;
constructor TSuperVisor.init;
var i: integer;
begin
GObjectsList.init;
Rect.assign(xa,ya,xb,yb);
SetColor(white);
Rectangle(xa,ya,xb,yb);

for i := 1 to 10 do
AddGObject(new(tpball, init(random(getmaxx - 20) + 10,
random(getmaxy - 20) + 10,
2, red, random(360), 1, 5)));
end;

function TSuperVisor.AddGObject(p:TPGObject);
begin
AddGObject:=GObjectsList.AddItem(p);
end;

function TSuperVisor.DeleteGObject(p:TPGObject):boolean;
begin
// DeleteGObject:=GObjectsList.DeleteItem(p);
end;

procedure TSuperVisor.main;
var p: TPItem;
begin

repeat

delay(10);
p := GObjectsList.List;
while p <> nil do begin
p^.data^.moveto;
p := p^.next;
end;

// При нажатии на Esc - выходим из цикла
if keypressed and (readkey = #27) then break
else
while keypressed do readkey;

until false;
end;

// Это - пока пустые функции, похоже, Calculation здесь вообще не нужна -
// вычислениями будет заниматься конкретный объект, а не Наблюдатель
function TSuperVisor.ChangeDirection: boolean;
begin end;
function TSuperVisor.Calculation: boolean;
begin end;
function TSuperVisor.WriteResults: boolean;
begin end;


destructor TSuperVisor.done;
begin
GObjectsList.done;
ClearDevice;
end;


var
grdriver, grmode, errcode: smallint;
sv: TSuperVisor;

begin
grDriver := d8bit; grMode := m800x600;
InitGraph(grDriver, grMode, '');
ErrCode := GraphResult;
if ErrCode <> grOk then begin
Writeln('Graphics error:', GraphErrorMsg(ErrCode));
writeln('Press Enter to halt()'); readln; halt(100);
end;


SV.init(1, 1, getmaxx, getmaxy);
SV.main;
SV.done;
end.
Bokul
Спасибо! smile.gif

Учел все замечания, исправил, извиняюсь что сам не отладил некоторые. unsure.gif

Отказ от ненужных переменных oldx,oldy избавил от надобности переопределять метод moveto в объекте TBall.
Также поставил в основной цикл отрисовку границ поля - без этого они затираются шариками.
И чуточку изменил деструктор TSuperVisor-а - вместо ClearDevice используем такой способ:

destructor TSuperVisor.done;
var bufcolor:byte;
begin
GObjectsList.done;
bufcolor:=GetColor;
SetColor(GetBkColor);
rectangle(rect.a.x,rect.a.y,rect.b.x,rect.b.y);;
SetColor(bufcolor);
end;

Измененные модули вмести с SuperVisor - Нажмите для просмотра прикрепленного файла

Цитата
С такой реализацией списка тоже можно жить... Неудобно, правда, но ничего - это FPC, в конце концов - перегрузишь операторы - будет смотреться получше...

Что ты имеешь ввиду? Может вместо списков использовать коллекции?

Цитата
несоответствие типов, копаться, чтобы исправить это я не стал, просто закомментировал вызов процедуры
Да, надо изменить на
function TSuperVisor.DeleteGObject(p:TPItem):boolean;

Цитата
т.к. не совсем понятно, почему при добавлении к списку функция AddItem получает указатель типа TPGObject, а при удалении DeleteItem требует указатель типа TPItem...

Для инициализации звена списка, объект UnitItem должен знать свою информационную часть, и по-этому мы передаем ему переменную типа TPGObject, но потом, при работе с UnitList-ом для удаления своего элемента все, что он должен - это указатель на звено, а не информационную часть (ведь таких звеньев может быть несколько). Вот почему в DeleteGObject как передаваемый параметр я использую TPItem.

Цитата
/// Это - пока пустые функции, похоже, Calculation здесь вообще не нужна -
// вычислениями будет заниматься конкретный объект, а не Наблюдатель

Я тоже согласен оставить эти методы абстрактными в TSuperVisor и переопределять их в наследниках.
По-поводу Calculation не совсем представляю как это будет. Ты хочешь передавать в TGObject точку, где он столкнулся, а все остальное пусть высчитывает сам?
volvo
Цитата
Я тоже согласен оставить эти методы абстрактными в TSuperVisor и переопределять их в наследниках.
А вот с этого места - поподробнее... Что это будут за наследники от TSuperVisor? СуперНаблюдатели? НаблюдателиСПравомВмешатьсяИПредотвратит
ьСтолкновение? Что ты вкладываешь в смысл "унаследоваться от объекта" в этом случае? Какие это тебе даст преимущества? Шарики будут так же кататься? Сталкиваться? Отскакивать друг от друга? Это все будет работать и с этим же Наблюдателем, без наследников...

Понимаешь в чем дело, ведь просто так наследование - оно не панацея... Его надо применять тогда, когда это необходимо, а не потому, что оно есть в языке... Вот я и прошу тебя объяснить, ЧЕМ программа с ПЕРЕопределенным Наблюдателем будет отличаться от программы, где методы TSuperVisor будут доработаны?

Цитата
Может вместо списков использовать коллекции?
Аналогично, какие ты в этом видишь преимущества? Что есть такое в коллекциях, чего нет (и нельзя сделать, заметь!!!) в списке? Только то, что там ЭТО уже готово, а для TList придется сделать - это для меня не аргумент, тем более в целях обучения... Я, конечно, против изобретения велосипедов, но вот тут как раз - не тот случай... Смотри: есть у меня коллекция. Что я могу с ней сделать (на примере TCollection)? Добавить элемент в коллекцию, найти его в коллекции, удалить оттуда, правда? А со списком все гораздо интереснее... Я же могу переопределить TList на TPriorityList, скажем, и добавлять в список объекты, согласно их приоритету... Например, для того, чтобы обработка квадратов была предпочтительнее обработки треугольников в 3 раза. И, заметь, я это полностью буду контролировать, ибо ЭТО - написано мной, а не кем-то, возможно, даже, лучше меня программирующим, НО не знающим специфики моего приложения... Мне, например, может быть критична скорость операции добавления в список, а по какому критерию оптимизированы коллекции, ты можешь мне гарантировать? smile.gif Я, наконец, список элементарно заменю на дерево, если надо будет... Так что ТАКИЕ вещи я предпочитаю делать сам; если ты хочешь пользоваться готовым - дело твое, только потом НЕ говори, что я тебя НЕ предупреждал, когда тебе станет не хватать скорости обработки, или емкости коллекции (ну, это - вряд ли, они достаточно вместительны), или гибкости ее... (Если тебя прельстил метод ForEach, то это тоже преодолимо, как ты помнишь...)

Цитата
Ты хочешь передавать в TGObject точку, где он столкнулся, а все остальное пусть высчитывает сам?
Да я бы вообще ничего не передавал... Чего, объект своего положения не знает? Зачем еще ему что-то передавать? Надо просто вызвать процедуру обработки столкновения двух объектов (причем она совершенно не обязательно будет методом TSuperVisor, я бы как раз ее сделал посторонней процедурой), а там уже пускай в зависимости от формы и свойств столкнувшихся объектов она разруливает, какой процент энергии будет потерян, и какой объект получит импульс под каким углом (и как именно она это будет делать - тоже никоим образом Наблюдателя не касается - это не его проблема, он Наблюдатель!!! Зафиксировал факт столкновения - сообщил, куда следует. Все, спасибо, продолжай наблюдать)... А дальше - прямая обязанность объекта - пересчитать свои координаты с учетом новых данных...
Bokul
Цитата
А вот с этого места - поподробнее... Что это будут за наследники от TSuperVisor? СуперНаблюдатели?
Вот я и прошу тебя объяснить, ЧЕМ программа с ПЕРЕопределенным Наблюдателем будет отличаться от программы, где методы TSuperVisor будут доработаны?

lol.gif
Думал наследием воспользоваться как инструментом поддержки новых типов наследников TGObject... но по твоему способу организации нужда в нем действительно отпадает.
Цитата
Если тебя прельстил метод ForEach, то это тоже преодолимо, как ты помнишь...

yes2.gif
Все, забыл, я даже такого слова никогда не слышал... smile.gif Оставляем списки, я только за.
Цитата
Надо просто вызвать процедуру обработки столкновения двух объектов (причем она совершенно не обязательно будет методом TSuperVisor, я бы как раз ее сделал посторонней процедурой), а там уже пускай в зависимости от формы и свойств столкнувшихся объектов она разруливает, какой процент энергии будет потерян, и какой объект получит импульс под каким углом (и как именно она это будет делать - тоже никоим образом Наблюдателя не касается - это не его проблема, он Наблюдатель!!! З

yes2.gif
С появлением все новых наследников TGObject-а TSuperVisor должен запускать все новые и новые процедуры (именно о них я писал в теме "Задачник по ООП", только хотел их всунуть в TSuperVisor), а для этого нам надо как-то дать знать TSuperVisor-у какие процедуры запускать в зависимости от столкнувшихся объектов. Может и бред, но мне на ум приходит сделать еще один список в TSuperVisor , который будет хранить записи типа

record
typeof1:pointer;//тип первого объекта
typeof2:pointer;//тип второго объекта
proc:@procedure; //процедура обработки
end;

и при появлении новых фигур просто добавлять в этот список новые процедуры.
volvo
Цитата
С появлением все новых наследников TGObject-а TSuperVisor должен запускать все новые и новые процедуры
no1.gif Совершенно не обязательно... Хочешь способ, по которому будет вообще ОДНА процедура
Procedure ComputeCollision(Var first_obj, second_obj: TGObject);
? Значится, так: при столкновении считаем, что произошло столкновение двух точечных объектов (точка столкновения, допустим, известна; векторы скоростей обоих объектов - тоже; возможно - даже коэффициенты упругости - если это неприменимо к понятию "точечный объект", заменим его понятием "абстрактный объект" и их массы; что еще надо?) Ты УЖЕ можешь по тем данным, которые я перечислил пересчитать траектории движения, так? Пересчитал, получил новый вектор скорости для first_obj и second_obj, и ... поменял на него старый - один дополнительный метод в классе TGObject...

Пока вижу только одну проблему - само обнаружение столкновения, ибо ту ссылку я так и не могу найти, а все, что находится - очень сложно, и, я боюсь, крайне медленно... Хотя для начала - вполне пойдет и вот такой простой алгоритм: ты в каждый момент времени знаешь, КУДА движется объект, так? Найти ту его точку, которая, скажем так, "движется впереди всех остальных" - то есть, первой вступит в соприкосновение с другим объектом/стенкой (я не говорю сейчас о случае, когда в данный объект врезалИСЬ, рассматривается только случай, что данный объект врезалСЯ в кого-то) сможешь? А проверить, находится ли точка с известными тебе координатами внутри фигуры (т.е., реализовать виртуальную функцию Function IsInside(P: TPoint): Boolean)?

Понимаешь, куда я клоню? Точка "движущаяся первой" - есть, проверяешь, не находится ли она случайно внутри другого объекта, и все, если находится (внутри или на границе) - то есть столкновение!!! Конечно, можно подобрать форму объекта, для которого такой вариант не будет работать, но в большинстве случаев достаточно простых фигур (не забывай, со сложнвми есть проблемы еще и при отрисовке, так что очень усложнять тоже нежелательно), мне кажется, проблем быть не должно... По крайней мере, попробуй это реализовать, даже если это ошибочно, ничего плохого не будет, если ты заставишь этот алгоритм работать хотя бы только на кругах/квадратах...
Bokul
Цитата
Ты УЖЕ можешь по тем данным, которые я перечислил пересчитать траектории движения, так?

Пересчет идет где? В TGObject?
Цитата
Найти ту его точку, которая, скажем так, "движется впереди всех остальных"

Для квадрата уже будет целая сторона в некоторых случаях..
volvo
Цитата
Пересчет идет где? В TGObject?
Зачем? Прямо в ComputeCollision - у тебя же "абстрактный объект"

Цитата
Для квадрата уже будет целая сторона в некоторых случаях..
Значит, придется запоминать массив/список "впереди-идущих" точек (если сторона - значит, конечно, не каждая ее точка, а, скажем, 5-10 равноотстоящих точек между началом и концом этой стороны включительно), и проверять каждую из них на попадание в другую фигуру... blum.gif

Собственно, полезные ссылки:
Определение столкновений выпуклых объектов движущихся с постоянными скоростями

Collision Detection and Impulse Dynamics in Real time Applications (это больше для 3D, но все равно интересно)

excl.gif
А вообще - у меня к тебе вопрос: КАКИЕ фигуры ты хочешь вводить в иерархию движущихся объектов? Перечисли ВСЕ фигуры, которые ты себе представляешь... (Лучше всего при ответе на этот вопрос не просто перечислить их, а, хотя бы, привести для каждой фигуры конструктор и методы Show/Hide, понимаешь, о чем я?)
Bokul
Спасибо! smile.gif

Извиняюсь, что заставил себя ждать, зато отладил некоторые модули и сделал реализацию твоего алгоритма - теперь шарики умеют отталкиваться. smile.gif
Изменения в структуре объектов:

TGObject
// - определяем впереди идущую точку, новая
function GetFrontPoint:TPoint; virtual;//abstract;
// - проверяем является ли P частью TGObject, новая
function IsInside(P:TPoint):boolean; virtual;//abstract;
// - теперь этот метод не содержит параметров
procedure ChangeDirection; virtual;//abstract;
TBall=object(TGObject)
// вместо двух переменных используем одну специализированную
pos:TPoint;
// переопределяем, теперь мы умеем менять направление польота
procedure ChangeDirection; virtual;
// переопределяем, новая
function GetFrontPoint:TPoint; virtual;
// переопределяем, новая
function IsInside(P:TPoint):boolean; virtual;
TSuperVisor=object
// проверяем не вышел ли объект за поля, новая
function IsInField(obj:TPGObject):boolean;
// проверяем столкнулись ли объекты, новая
function IsCollision(first_obj,second_obj:TPGObject):boolean;
// эта процедура обзавелась новым циклом
procedure main;

ChangeDirection, Calculation, WriteResults - абстрактные до этого времени методы я удалил.

Других объектов я не трогал..


Очередная поставка модулей - Нажмите для просмотра прикрепленного файла
Если администрация разрешает - Exe-шник :Нажмите для просмотра прикрепленного файла

Цитата
Значит, придется запоминать массив/список "впереди-идущих" точек (если сторона - значит, конечно, не каждая ее точка, а, скажем, 5-10 равноотстоящих точек между началом и концом этой стороны включительно), и проверять каждую из них на попадание в другую фигуру...

Это я оставил на следующий этап - пока только одна точка sad.gif .

Понаблюдав чуть-чуть за шариками, можно увидеть ситуации, когда они "слипаются" и летят вмести - знак того, что алгоритм имеет недостатки и его надо менять sad.gif . Прочитал ссылку по столкновениям выпуклых объектов, в коде ни черта не разобрал, но идея проекций понравилась. good.gif Наверное так и надо...

Цитата
понимаешь, о чем я?

Ты имеешь ввиду, буду ли я делать вогнутые объекты? Не знаю, пока - нет... smile.gif
volvo
Цитата
Других объектов я не трогал..
Реализация TSupervisor осталась за кадром, ты не прикрепил основной файл, поэтому судить о том, насколько правильно ты реализовал предложенная мной алгоритм, я не могу...
Bokul
Да я вроде все модули прикреплял blink.gif , вот TSupervisor отдельно:

Нажмите для просмотра прикрепленного файла
volvo
Цитата(Bokul @ 24.12.2006 2:49)
Понаблюдав чуть-чуть за шариками, можно увидеть ситуации, когда они "слипаются" и летят вмести - знак того, что алгоритм имеет недостатки и его надо менять sad.gif .

no1.gif Понаблюдав за твоим кодом и сделав несколько экспериментов над ним, я могу тебе рассказать следующее:

во-первых, ты опять тратишь лишние вычислительные ресурсы... На этот раз - здесь:
  function TBall.distance(P:TPoint):real;
begin
distance:= sqrt(sqr(p.x-pos.x)+sqr(p.y-pos.y));
end;
Вот расскажи мне, зачем тебе нужен ТУТ корень? Что, недостаточно было возвращать целый квадрат расстояния и сравнивать его с квадратом радиуса? Это ж ускорение какое... Мало того, что операции над целыми выполняются гораздо быстрее, чем над вещественными числами, так еще и "дорогая" Sqrt не нужна, а квадрат радиуса можно вычислять и в конструкторе...

Во-вторых... Попробовав уменьшить число шариков до 2-х я с удивлением обнаружил, что проверка на выход за пределы Rect не всегда корректно срабатывает (даже, если шарики вообще не сталкиваются, а идут параллельными курсами blink.gif )... !smoke2.gif Попробуй, увидишь...

Теперь еще одно... Что ты делаешь... Ты проходишь по списку шариков, и для каждого из них запускаешь еще один проход по списку... Все прекрасно, НО... Ты не боишься, что при проверке
               if not(IsInfield(p^.data)) then
p^.data^.ChangeDirection;
ты вторично изменяешь направление движения какого-нибудь шарика? Вот тут и будет наблюдаться "слипание"...

Итого, программа тебе на сегодня: проверить и отладить IsInField, и придумать такой механизм, который запрещал бы вторично менять направление движения одному и тому же шарику...

Кстати, чуть не забыл... Абстрактность метода может задаваться директивой Abstract, тогда реализацию (эту самую "пустышку") вообще не надо писАть... yes2.gif

Теперь вроде все...
Bokul
Цитата
Вот расскажи мне, зачем тебе нужен ТУТ корень? Что, недостаточно было возвращать целый квадрат расстояния и сравнивать его с квадратом радиуса? Это ж ускорение какое... Мало того, что операции над целыми выполняются гораздо быстрее, чем над вещественными числами, так еще и "дорогая" Sqrt не нужна, а квадрат радиуса можно вычислять и в конструкторе...

Да ты - прав, выигрыш видно сразу, особенно когда количество объектов достигает несколько сотен..
Цитата
Во-вторых... Попробовав уменьшить число шариков до 2-х я с удивлением обнаружил, что проверка на выход за пределы Rect не всегда корректно срабатывает (даже, если шарики вообще не сталкиваются, а идут параллельными курсами blink.gif )... !smoke2.gif Попробуй, увидишь...

Что именно? То, что они отбиваются не под правильным углом? Или может они улетают из поля?
PS не знаешь формулы расчета угла после удара? Я вывел свою, ну как видешь она не совсем корректно работает sad.gif ...
volvo
Цитата
Что именно?
А именно - вот что (эту картинку я наблюдал сам): представь себе 2 шарика, двигающихся параллельно друг другу из правого верхнего в левый нижний угол экрана... НО как только первый шар касается нижней границы и меняет направление (кстати, меняет он направление не по закону "угол падения равен углу отражения", а на противоположное - на 180 градусов), то же самое делает и второй шар (несмотря на то, что он-то еще не дошел до границы по крайней мере 5 своих диаметров)... unsure.gif А почему это происходит - я так и не понял, надо подольше поэкспериментировать...

Кстати, где-то на форуме я как-то выкладывал ссылку на физическую модель соударения упругих шаров (если она еще живая) - попробуй поискать, что-то связанное с упруг* / биллиа* / модель
Bokul
Цитата
НО как только первый шар касается нижней границы и меняет направление, то же самое делает и второй шар

Не выходит спроектировать тоже самое (хотя, когда много шаров я видел ситуации, как шар менял направления не долетая до стенки), можешь дать данные для двух шаров, когда это происходит?
Цитата
кстати, меняет он направление не по закону "угол падения равен углу отражения", а на противоположное - на 180 градусов

no1.gif Вот процедура (переделанная под функцию для примера), отвечающая за это:
Код

uses math;
function ChangeDirection(l:real):real;
      function mydiv(a,b:real):longint;
      begin
        mydiv:=trunc(a/b);
      end;
      function mymod(a,b:real):real;
      begin
        mymod:=a-mydiv(a,b)*b;

      end;
const l90=pi/2;
begin

  ChangeDirection:=mydiv(l,l90)*l90 + (pi-mymod(l,l90));
end;

begin
  writeln(radtodeg(ChangeDirection(degtorad(120))):0:2);
  readln;
end.

Как видишь она не получает никаких внешних данных, но это не правильно. Почему? Вот Нажмите для просмотра прикрепленного файла .
Выходит нам надо передавать этой процедуре угол (T) под каким располагается поверхность об какую ударился шарик. И новый угол полёта = T+L где L - угол до соударения.

Но так как метод ChangeDirection один для всех (и для случаев столкновения с другими шарами), то нужно знать под каким углом(P) соударяются объекты, для шариков он будет таковым:
P:=arctan((y2-y1)/(x2-x1))+90
x1,y1- координаты центра (точка А на рисунке) первого шара
x2,y2- координаты центра (точка В на рисунке) второго шара
Нажмите для просмотра прикрепленного файла
Угол P должен высчитывать TSupervisor, но в зависимости от столкнувшихся объектов, он будет вычисляться по разному. Как предлагаешь TSupervisor-у без наследия определять этот угол для любых возможных наследников TGObject?
volvo
Цитата
Как предлагаешь TSupervisor-у без наследия определять этот угол для любых возможных наследников TGObject?
Ты не забыл, что у каждого объекта есть вектор скорости? И вычисление угла разлета можно производить именно по нему?
Bokul
Цитата
Ты не забыл, что у каждого объекта есть вектор скорости? И вычисление угла разлета можно производить именно по нему?

yes2.gif
Ты предлагаешь передавать объекту точку столкновения, которая имеет скорость и координаты, а все остальное пусть вычисляет сам? С этим я согласен. Если не понятно, что я хочу сказать, скажи. yes2.gif
volvo
Что-то обсуждение зацикливается... Я же написал тебе выше - я бы сделал НЕметод класса CalcCollision который получает два объекта (любых), которые могут сообщить всю информацию о себе: где находится сейчас (координаты центра), куда летит, какой у него коэффициент упругости, скорость, и т.д.

Зная подобную информацию о двух объектах можно с легкостью вычислить параметры движения после столкновения, и передать их объектам... Или ты хочешь, чтобы квадрат с треугольником разлетались по другим законам, нежели два круга?

Кстати, о скорости... Я бы все-таки предпочел работать с
TVector = Record
X, Y: Double;
End;

(т.е. вертикальная/горизонтальная составляющая, а не угол относительно одной из осей...) Тогда, например, отскок от стенки будет тривиальным - смена знака одной из составляющих. Да и возможности FPC одним выражением складывать/умножать/вычитать/масштабировать вектора тоже нельзя не принимать во внимание - это может очень сильно облегчить работу... rolleyes.gif
Bokul
Цитата
Что-то обсуждение зацикливается...

Ага, просто надо разобраться до конца и потом уже кодить... smile.gif
Цитата
Или ты хочешь, чтобы квадрат с треугольником разлетались по другим законам, нежели два круга?

Из-за этого, наверное, и получилась неразбериха. Да, но я согласен сделать сначала твой способ, а уже потом его расширить.. smile.gif

Цитата
Да и возможности FPC одним выражением складывать/умножать/вычитать/масштабировать вектора тоже нельзя не принимать во внимание - это может очень сильно облегчить работу..

???
Что именно он умеет?

PS значит у меня сейчас не правильно работает объекты, ведь шарик сам просчитывает свою новую траекторию?

volvo
Цитата
Что именно он умеет?
blink.gif

Var v, v1, v2: TVector;
...
v := k * (k1 * v1 + k2 * v2);

Сколько тебе понадобится строк в Турбо Паскале, чтобы это реализовать? В FPC пишется именно так, как я показал, это что, не облегчает работу? wink.gif Ты именно программируешь алгоритм, а не думаешь, "а позволит ли мне ЭТО язык, или надо какой костыль изобрести?"

Hint: operator overloading

Цитата
Значит у меня сейчас не правильно работает объекты, ведь шарик сам просчитывает свою новую траекторию?
Просчет просчету рознь... Если шарик не взаимодействует со внешними объектами, а рассчитывает свое новое местоположение на основе изначально заданных параметров - то все правильно... А вот заставлять сам шарик пересчитывать эти самые параметры, когда было внешнее воздействие (вместо того, чтобы централизованно пересчитать, и передать шарику уже новые параметры) - вот это уже, на мой взгляд, неверно...
Bokul
Мысли в слух:
Ведь мы полностью не сможем скопировать настоящую математическую модель графических объектов (шариков, квадратов и т.п.) - нельзя постоянно проверять каждую точку каждого объекта на наявность столкновения с другим. Остается удовлетворятся только некоторыми точками. Но это надо сделать максимально реалистически и легко изменяемо. Возникает необходимость создание предка-объекта, отвечающего за математическое представление граф. объекта. Я предлагаю сделать этот объекта-предок в виде многоугольника с любым числом вершин (чем больше количество последних - тем более приближенна реализуема модель к ее реальному собрату (настоящему шару)).
Вод пример для шара:
Нажмите для просмотра прикрепленного файла
Bokul
Какие преимущества: больше не надо мучится со способами нахождения точки столкновения, угла разлёта и т.д.
Недостатки: интересует не сильно ли это будет тормозить.. sad.gif
volvo
Будет тормозить, конечно... Представь, что у тебя, скажем, 4, а лучше - 8 точек в модели (еще лучше - 16/32, ибо это будет еще ближе к "шару", но тогда процессор просто повесится). И 100 шариков... И каждый раз ты будешь проверять, 4 (или 8) точек, "а одна из этих точек не внутри ли какого-нибудь из оставшихся 99 шаров?"... Представляешь себе объемы вычислений? 100*100 - сразу 10000 проверок (даже по одной точке, по 4-м будет уже с полсотни тысяч) А ведь еще надо отрисовывать картинку, да и проверять столкновение со стенками... Да и сами проверки - не просто 2+2, а нечто более сложное...

Придется принимать доп. меры для сокращения объемов вычислений...

Как вариант - могу предложить следующее (просто набросок, возможно это будет не очень эффективно, на какой-то прирост производительности, определенно даст):
Хранить не 1, а 2 списка объектов... Да, да, именно 2 (если даже не больше)... Смотри, что я имею в виду: Когда объект A может столкнуться с объектом B? Только тогда, когда Sign(A.Vx) <> Sign(B.Vx), т.е. проекции оббъектов на ось OX приближаются друг к другу... Вот тебе и задумка: хранить в списке ListOne только те объекты, которые движутся слева направо или по вертикали, т.е. Vx неотрицательна, а в ListTwo - все остальные... Преимущества - для каждого объекта из ListOne надо проверять на коллизии только объекты из ListTwo, в среднем - 2-х кратное уменьшение числа вычислений...

Теперь ты меня, конечно, спросишь: а что будем делать, когда Vx меняет знак (отскок от стены млм друг от друга)? А ничего особенного. Все равно есть вот этот кусок кода:

p := GObjectsList.List;
while p <> nil do begin
p^.data^.moveto;
p := p^.next;
end;

Вот, после MoveTo и проверять, изменился ли знак Vx... Если изменился - то удалить указатель на объект из одного списка, и добавить в другой... Для обоих списков (в худшем случае) "+200" операций... По сравнению с экономией примерно 20000-25000, о чем я говорил выше - будет ускорение обработки...

НО: для этого во-первых надо будет перейти на представление скорости через Vx/Vy, о чем я уже тебе говорил, а во-вторых - в класс TItem придется добавить флажок типа Boolean, чтобы для того объекта, для которого уже вызывалась MoveTo, не вызвать ее еще раз... И тогда:

// Вначале, конечно, идем по одному списку
p := GFirstObjectsList.List;
while p <> nil do begin
p^.data^.moveto;
// если знак p^.data^.Vx изменился (вполне возможно, что об этом может
// сигнализировать и сама moveto, которую просто переделать в функцию)
// - то скопировать p^.data в список GSecondObjectsList, и удалить из текущего
// !!! special_flag (что это - см. ниже) для перенесенного объекта установить в True

p := p^.next;
end;

// А теперь, собственно, по второму:
p := GSecondObjectsList.List;
while p <> nil do begin

// вот это - тот самый флажок, о котором я говорил
// установленный в True он запретил вызывать moveto для объекта, если был
// False - то все в порядке, это НЕ только что добавленное в этот список значение

if NOT p^.data^.special_flag then begin { <--- Исправлено !!! }
p^.data^.moveto
// если знак p^.data^.Vx изменился - все с точностью "до наоборот" тому,
// что написано выше - переносим из Second в First список, только special_flag
// оставляем False
end
else p^.data^.special_flag := false;
// Все, теперь это - полноценный объект, и к нему будут применяться
// все операции, что и к остальным

p := p^.next;
end;
Мне такая идея отсечения нравится - как я раньше не додумался, надо будет как-нибудь попробовать... smile.gif
volvo
Сорри, ошибочка... Не будет работать для двух объектов, движущихся вертикально навстречу друг другу... Надо подумать, что с этим сделать - или хранить объекты с Vx = 0 в обоих списках, или при Vx = 0 (я не думаю, что таких случаев будет очень много) проверять на коллизии оба списка...
Bokul
Цитата
для этого во-первых надо будет перейти на представление скорости через Vx/Vy, о чем я уже тебе говорил

Уже сделано smile.gif Правда я оставил угол L для совместимости с предыдущими версиями smile.gif ... Да нет - шучу, он надо для расчета впереди идущей точке. Т.е. модель графического объекта = эта точка + то, что я описал раньше.

Цитата
Мне такая идея отсечения нравится - как я раньше не додумался, надо будет как-нибудь попробовать..

good.gif
Сделаем, у меня объект четырехугольника уже готов, остается его переделать в многоугольник. Только один вопросик:
Цитата
Вот, после MoveTo и проверять, изменился ли знак Vx...

А может слежения за изменением ставить для метода ChangeDirection? Ведь знак меняется только в случае вызова этой процедуры.. Т.е. при вызове ChangeDirection флажок изменения ставим в true, при вызове MoveTo - в false. unsure.gif

Цитата
Сорри, ошибочка... Не будет работать для двух объектов, движущихся вертикально навстречу друг другу... Надо подумать, что с этим сделать - или хранить объекты с Vx = 0 в обоих списках, или при Vx = 0 (я не думаю, что таких случаев будет очень много) проверять на коллизии оба списка...

Может тогда сделать четыре списка?
Каждый цвет отвечает одному из четырёх списков:
Нажмите для просмотра прикрепленного файла
volvo
Цитата
Т.е. при вызове ChangeDirection флажок изменения ставим в true, при вызове MoveTo - в false.
no1.gif Ты не понял... Еще раз перечитай... Флажок нужен, чтобы MoveTo (в смысле, пересчет координат) не вызывался еще раз после переноса указателя из одного списка в другой (при обработке второго списка), но ведь не только тот объект, который изменил направление, должен пересчитать координаты? Пересчитывать должны ВСЕ, но ни один не должен этого делать дважды...

Цитата
Т.е. при вызове ChangeDirection флажок изменения ставим в true
С этим согласен... После ChangeDirection устанавливаем флаг в True, и переносим объект в другой список... А вот сбрасывать надо именно так, как я показал - а не по MoveTo...

Цитата
Может тогда сделать четыре списка?
Надо подумать... У тебя тут угол, а не проекция Vx... Все-таки, скорее всего будет достаточно двух, и при нулевом Vx - проверять оба списка...
Bokul
Реализация Многоугольник окончена. Возник вопрос - где инициализировать вершины полигона? В SuperVisor или в конструкторе графического объекта?
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.