Results 1 to 9 of 9

Thread: Object Oriented programin (step one of many)

  1. #1

    Object Oriented programin (step one of many)

    Hi everyone,

    So I've decided to get deeper into using Object Oriented programming, this is first of many Object Oriented programming tests I'll be making for the sake of learning it...

    What I created now is a TMonster class which has a few parameters, procedures ... I create 10 Ogres from this which randomly attack each other, excluding themselves and dead ogres...

    Please take a look at my code , and tell me what not to do , somehow I feel I'm using parameters incorrectly...or the naming is not correct , even though it is working....

    Code:
    unit Unit1;
    interface
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Grids, Math,
      Vcl.ExtCtrls;
    type
      TForm1 = class(TForm)
        Button1: TButton;
        StringGrid1: TStringGrid;
        Memo1: TMemo;
        Timer1: TTimer;
        procedure FormCreate(Sender: TObject);
        procedure Timer1Timer(Sender: TObject);
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
      TMonster = class
        private
          myname : string;  // monster name
          health : smallint;
          myarmor : byte; // 0-255
          myattack : byte; // 0-255
          mytargetID : byte; // targetID
          isDead : boolean;
        published
          property name : string  // monsters name
            read myname;
          property life : smallint   // get's life for monster
            read health;
          property armor : byte // get's armor for monster
            read myarmor;
          property attack : byte // get's attack for monster
            read myattack;
          property target : byte // actual target (random)
            read mytargetID;
          property Dead : boolean  // am I dead
            read isDead;
        constructor Create (name : string ; life : byte ; armor : byte ; attack : byte);
        procedure TargetID( id : byte);
        procedure OnHit( attack : byte );
        procedure Died();
      end;
    var
      Form1: TForm1;
      Ogre : array of TMonster;
    
    implementation
    {$R *.dfm}
    constructor TMonster.Create(name : string ; life: Byte ; armor : byte ; attack : byte);
    begin
      myname := name;
      health := life;
      myarmor := armor;
      myattack :=attack;
      isDead := false; // by default alive
    end;
    procedure TMonster.TargetID(id: Byte);
    begin
      myTargetID := id;
    end;
    procedure TMonster.OnHit(attack: Byte);
    begin
      if (attack - armor) > 0 then
        health := health - ( attack - armor );
      // check if monster went to Himmel (heaven in germany)
      Died();
    end;
    procedure TMonster.Died;
    begin
      if health <= 0 then
        isDead := true;
    end;
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      Timer1.Enabled:=true;
    end;
    procedure TForm1.FormCreate(Sender: TObject);
    var i : integer;
    begin
      // we have 10 little ogre's
      SetLength(Ogre,10);
      // each of them has a name , and healtpoints , armor, attack
      Ogre[0] := TMonster.Create('William',100,1,1);
      Ogre[1] := TMonster.Create('William 2nd',120,1,1);
      Ogre[2] := TMonster.Create('Robert the Grumpy',145,2,3);
      Ogre[3] := TMonster.Create('Antal the SpaceOgre',150,4,3);
      Ogre[4] := TMonster.Create('Sebastian the Unstopable',160,1,4);
      Ogre[5] := TMonster.Create('Blorb the Blorb',99,5,6);
      Ogre[6] := TMonster.Create('Shrek',49,2,4);
      Ogre[7] := TMonster.Create('Devid the Serbian Ogre',180,4,6);
      Ogre[8] := TMonster.Create('Keen the Wise Ogre',167,6,9);
      Ogre[9] := TMonster.Create('Ogre the Ogre',98,6,11);
      StringGrid1.RowCount:=11;
      StringGrid1.Cells[0,0]:='Name';
      StringGrid1.Cells[1,0]:='Health';
      StringGrid1.Cells[2,0]:='Armor';
      StringGrid1.Cells[3,0]:='Attack';
      for I := 0 to 9 do
        begin
          StringGrid1.Cells[0,i+1]:=Ogre[i].name;
          StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].life);
          StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
          StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
        end;
    end;
    procedure TForm1.Timer1Timer(Sender: TObject);
    var i,j,target: integer;
        AvailableTarget : array of integer;
    begin
      randomize();
      Memo1.Lines.Clear;
      // fill array with alive monsters
      SetLength(AvailableTarget,0);
      for i := 0 to 9 do
        begin
          if Ogre[i].Dead = false then
            begin
              SetLength(AvailableTarget, length(AvailableTarget)+1);
              AvailableTarget[High(AvailableTarget)]:=i;
            end;
        end;
    
      for I := 0 to 9 do
        begin
          if Ogre[i].Dead = false then  // can only attack if alive
            begin
              target := AvailableTarget[random(length(AvailableTarget))]; // target is choosen from array of alive monsters
              if i <> target then  // will not attack self
                begin
                  Ogre[i].mytargetID:=target;
                  Ogre[target].OnHit(Ogre[i].attack);
                  Memo1.Lines.Add(Ogre[i].name+' attacks '+inttostr(Ogre[i].target)+' '+Ogre[Ogre[i].target].name);
                end;
            end;
        end;
    
    
      // update ogre stats
      for I := 0 to 9 do
        begin
          StringGrid1.Cells[0,i+1]:=Ogre[i].name;
          StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].life);
          StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
          StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
        end;
      // if only one target is available , means we have a winner, everyone else is dead
      if length(AvailableTarget) = 1 then
        begin
          Timer1.Enabled:=false;
          for i := 0 to 9 do
            begin
              if Ogre[i].isDead = false then
                Memo1.Lines.Add('The Winner is : '+ Ogre[i].name);
            end;
        end;
    end;
    end.

    Next step would be maybe to create some sorta very simple window app, where I would have multiple monster types :

    Ogre = Red Box
    Troll = Blue Box
    Imp = Green Box
    Human = Black Box

    then Ogre's would be sworn enemies of Troll's and Imp's
    Imp's would hate Humans
    Troll's would be enemies of Humans
    and Humans of each three, Ogre, Troll, Imp...

    I would be able to add to window per button new monster type with specific starting position , it would move in one way...if hit's wall , or some sort of other obstruction would randomly
    choose between available directions...
    if enemies would go to close to each other less then one block (32x32) , they would fight till one dies...

    Naturally I'm right now a bit far from this... but will start working on this immediately.... this way it's fun to learn...


    Greetings
    Robert

  2. #2
    Here are several suggestions:
    1. In pascal it is common that most private variables have a letter F as prefix. So instead of Health you would us FHealth. This way you can have verry similar names for private variables and prperties. This increases your codea readabiliy a lot.
    2. Replace mytargetID varaible with pointer to monster class. This way you allow yourself to easily get certain information from that monster (Position, Health, MosterType, etc.) by simply accesing property of that class (no need to search through a list of all monsters to find which has apropriate ID).
    3. In OnHit procedure you are substracting armor value from attack (damage) value and then checking to see if that is larger than 0 while you could easily just check to see if armor value is greater than attack value.
    Or even better simpy substract armor value from attack value and if that is largen than 0 reduce the health by that result.
    Code:
    procedure OnHit(attack: Byte);
    var Damage: Integer;
    begin
        damage := attack - armor; //Reduce damage by armor
        armor := armor - attack; //Reduce armor durability
        if armor < 0 then armor := 0; //to avoiud armor being negative mumber
        if damage> 0 then
        begin
            Healt := Health - damage; //Reduce health
            if Health <= 0 isDead := True;
       end;
    end;
    This way you also get rid of Died procedure.
    4. I also recomend learning about class inheritance: Why? By using class inheritance you make one ancestor class for all units which contains variables common to all of your ingame units (health, postition, unit type, name, description, etc.). And in inherited classes you can add aditional variables which are specific for certain unit types (range for ranged units, armor for armored units, etc.). And becouse Objective Pascal alows you to acces any inherited class by using it paretn class you can acces all the variables declared in parent class without the use of actuall clas of your object.

    Man I realy need to finish my Tutorial on working with classes as it would be of great help to you and probably many others.

  3. #3
    Hi ,

    I'm afraid I don't understand this part, to tell you the truth I "feel" like there has to be a way to be able to handle the "random" monster selection from within the class ..but I have no idea how.

    For example I create monster... and it somehow check for available monsters to attack, but how would this newly created monster know if there are at all other monsters to attack, or how many monsters
    are there (sorry for the confusion, I'm a bit confused myself to)

    Right now it's really simple because everyone can attack everyone, but say if there would be groups of friendly units, and group of enemy units...I would have problems.

    2. Replace mytargetID varaible with pointer to monster class. This way you allow yourself to easily get certain information from that monster (Position, Health, MosterType, etc.) by simply accesing property of that class (no need to search through a list of all monsters to find which has apropriate ID).


    Also I've altered the code, seems I have to use strict private...otherwise private values are visible...and modifiable....outside of the class...from TForm for example....

    So , private are all with F ,FArmor, FHealth, from within class on the OnHit procedure for example is it ok to use Health or FHealth ?

    Code:
    unit Unit1;
    
    interface
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Grids, Math,
      Vcl.ExtCtrls;
    type
      TForm1 = class(TForm)
        Button1: TButton;
        StringGrid1: TStringGrid;
        Memo1: TMemo;
        Timer1: TTimer;
        procedure FormCreate(Sender: TObject);
        procedure Timer1Timer(Sender: TObject);
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
      TMonster = class
        strict private  // othervise private is visible
          FName : string;
          FHealth : smallint;
          FArmor : smallint;
          FAttack : smallint;
          FTargetID : byte;
          FDead : boolean;
        published
          property Name : string
            read FName write FName;
          property Health : smallint
            read FHealth write FHealth;
          property Armor : smallint
            read FArmor write FArmor;
          property Attack : smallint
            read FAttack write FAttack;
          property TargetID : byte
            read FTargetID write FTargetID;
          property Dead : boolean
            read FDead write FDead;
        constructor Create (name : string ; health : byte ; armor : byte ; attack : byte);
        procedure OnHit( attack : byte );
    
      end;
    var
      Form1: TForm1;
      Ogre : array of TMonster;
    
    implementation
    {$R *.dfm}
    constructor TMonster.Create(name : string ; health: Byte ; armor : byte ; attack : byte);
    begin
      FName := name;
      FHealth := health;
      FArmor := armor;
      FAttack :=attack;
      FDead := false; // by default alive
    end;
    
    procedure TMonster.OnHit(attack: Byte);
    var damage : smallint;
    begin
        damage := attack - armor; //Reduce damage by armor
        if damage > 0 then
          begin
            Health := Health - damage; //Reduce health
            if Health <= 0 then
              begin
                Dead := True;
                Health :=0;
              end;
          end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      Timer1.Enabled:=true;
    end;
    procedure TForm1.FormCreate(Sender: TObject);
    var i : integer;
    begin
      // we have 10 little ogre's
      SetLength(Ogre,10);
      // each of them has a name , and healtpoints , armor, attack
      Ogre[0] := TMonster.Create('William',100,1,1);
      Ogre[1] := TMonster.Create('William 2nd',120,1,1);
      Ogre[2] := TMonster.Create('Robert the Grumpy',145,2,3);
      Ogre[3] := TMonster.Create('Antal the SpaceOgre',150,4,3);
      Ogre[4] := TMonster.Create('Sebastian the Unstopable',160,1,4);
      Ogre[5] := TMonster.Create('Blorb the Blorb',99,5,6);
      Ogre[6] := TMonster.Create('Shrek',49,2,4);
      Ogre[7] := TMonster.Create('Devid the Serbian Ogre',180,4,6);
      Ogre[8] := TMonster.Create('Keen the Wise Ogre',167,6,9);
      Ogre[9] := TMonster.Create('Ogre the Ogre',98,6,11);
      StringGrid1.RowCount:=11;
      StringGrid1.Cells[0,0]:='Name';
      StringGrid1.Cells[1,0]:='Health';
      StringGrid1.Cells[2,0]:='Armor';
      StringGrid1.Cells[3,0]:='Attack';
      for I := 0 to 9 do
        begin
          StringGrid1.Cells[0,i+1]:=Ogre[i].name;
          StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].Health);
          StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
          StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
        end;
    end;
    procedure TForm1.Timer1Timer(Sender: TObject);
    var i,target: integer;
        AvailableTarget : array of integer;
    begin
      randomize();
      Memo1.Lines.Clear;
      // fill array with alive monsters
      SetLength(AvailableTarget,0);
      for i := 0 to 9 do
        begin
          if Ogre[i].Dead = false then
            begin
              SetLength(AvailableTarget, length(AvailableTarget)+1);
              AvailableTarget[High(AvailableTarget)]:=i;
            end;
        end;
    
      for I := 0 to 9 do
        begin
          if Ogre[i].Dead = false then  // can only attack if alive
            begin
              target := AvailableTarget[random(length(AvailableTarget))]; // target is choosen from array of alive monsters
              if i <> target then  // will not attack self
                begin
                  Ogre[i].TargetID:=target;
                  Ogre[target].OnHit(Ogre[i].attack);
                  Memo1.Lines.Add(Ogre[i].name+' attacks '+inttostr(Ogre[i].TargetID)+' '+Ogre[Ogre[i].TargetID].name);
                end;
            end;
        end;
    
    
      // update ogre stats
      for I := 0 to 9 do
        begin
          StringGrid1.Cells[0,i+1]:=Ogre[i].name;
          StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].Health);
          StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
          StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
        end;
      // if only one target is available , means we have a winner, everyone else is dead
      if length(AvailableTarget) = 1 then
        begin
          Timer1.Enabled:=false;
          for i := 0 to 9 do
            begin
              if Ogre[i].Dead = false then
                Memo1.Lines.Add('The Winner is : '+ Ogre[i].name);
            end;
        end;
    end;
    end.

  4. #4
    Hi,

    I've continued with my next step , which in the end turned out harder then I thought (atleast for me) ...
    What I did is...define a map now, and draw it onscreen using shapes... then I've created my monster class which can navigate inside this maze...
    Directions are choosen from available directions on random when I hit the wall...

    I think it turned out quiet good...anyway I am dead tired , here is the code, I'm off to play one more goodnight SC2 , then off to sleep.

    Code:
    unit MainFrm;
    // idea is
    // I check available directions...
    // then I set a direction
    // when I would hit a wall...I check again available directions...
    // then I set a direction...
    // could be good
    interface
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
    type
      TMain = class(TForm)
        Panel1: TPanel;
        Panel2: TPanel;
        Button1: TButton;
        Button2: TButton;
        Timer1: TTimer;
        Edit1: TEdit;
        procedure DrawMap();
        procedure DrawMonster();
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure Timer1Timer(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
      TDirection = ( dUp, dDown , dLeft, dRight );
      TMonster = class
        strict private
          FName : string;
          FStartX,FStartY : byte;
          FSetDir : TDirection;
          Direction : array of TDirection;
        published
          property Name : string
            read FName write FName;
          property StartX : byte
            read FStartX write FStartX;
          property StartY : byte
            read FStartY write FStartY;
          property SetDir : TDirection
            read FSetDir write FSetDir;
          constructor Create( Name : string ; StartX,StartY : byte ; SetDir : TDirection );
          procedure GetPossibleDirections();
          procedure OnMove;
      end;
    
    
    
      TMapLine = array [0..19] of Integer;
      TMap = array [0..14] of TMapLine;
    const
      Map: Array [0..14,0..19] of Integer = ((1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1,1,1,1,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,1),
                                             (1,0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,0,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1));
    
    var
      Main: TMain;
      Monster : array of TMonster;
      // hacky way later fix
      AI : array of TShape;
    
    
    implementation
    {$R *.dfm}
    constructor TMonster.Create(Name: string ; StartX,StartY : byte; SetDir : TDirection);
    begin
      FName := Name;
      FStartX := StartX;
      FStartY := StartY;
      FSetDir := SetDir;
    end;
    procedure TMonster.GetPossibleDirections;
    begin
      // debug
      Main.Edit1.Text:='';
      // empty directions
      SetLength(Direction,0);
      // no wall found up, way is free
      if Map[StartY-1,StartX] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dUp;
          Main.Edit1.Text:=Main.Edit1.Text+' UP ';
        end;
      // no wall found down, way is free
      if Map[StartY+1,StartX] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dDown;
          Main.Edit1.Text:=Main.Edit1.Text+' DOWN ';
        end;
      // no wall found left , way is free
      if Map[StartY,StartX-1] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dLeft;
          Main.Edit1.Text:=Main.Edit1.Text+' LEFT ';
        end;
      // no wall found right , way is free
      if Map[StartY,StartX+1] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dRight;
          Main.Edit1.Text:=Main.Edit1.Text+' RIGHT ';
        end;
    end;
    procedure TMonster.OnMove;
    begin
      if SetDir = dUp then
        begin
          if Map[StartY-1,StartX] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dDown then
        begin
          if Map[StartY+1,StartX] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dRight then
        begin
          if Map[StartY,StartX+1] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dLeft then
        begin
          if Map[StartY,StartX-1] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dUp then
        StartY:=StartY-1;
      if SetDir = dDown then
        StartY:=StartY+1;
      if SetDir = dRight then
        StartX:=StartX+1;
      if SetDir = dLeft then
        StartX:=StartX-1;
    
    end;
    procedure TMain.Button1Click(Sender: TObject);
    var i : integer;
    begin
      DrawMap();
      // we do 2 monsters for now
      SetLength(Monster,2);
      SetLength(AI,2);
      Monster[0] := TMonster.Create('Robert',1,1,dDown);
      Monster[1] := TMonster.Create('Devid',3,1,dDown);
      for i := 0 to length(Monster)-1 do
        begin
          AI[i] := TShape.Create(self);
          AI[i].Parent:=Panel2;
          AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
          AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
          AI[i].Height:=Panel2.Height div 15;
          AI[i].Width:=Panel2.Width div 20;
          AI[i].Brush.Color:=clGreen;
          AI[i].Pen.Color:=clGreen;
          AI[i].Visible:=true;
        end;
    end;
    procedure TMain.Button2Click(Sender: TObject);
    begin
      Timer1.Enabled:=true;
    end;
    procedure TMain.DrawMonster;
    var i : integer;
    begin
      // re-draw monster at new position
      for i := 0 to length(Monster)-1 do
        begin
          AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
          AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
        end;
    end;
    procedure TMain.Timer1Timer(Sender: TObject);
    begin
      Monster[0].OnMove;
      Monster[1].OnMove;
      DrawMonster();
    end;
    procedure TMain.DrawMap;
    var x,y: integer;
        Wall : TShape;
    begin
        for y := 0 to 14 do
          for x := 0 to 19 do
              begin
                if Map[y,x] = 1 then
                  begin
                    Wall := TShape.Create(self);
                    Wall.Parent:=Panel2;
                    Wall.Top:=y*(Panel2.Height div 15);
                    Wall.Left:=x*(Panel2.Width div 20);
                    Wall.Height:=Panel2.Height div 15;
                    Wall.Width:=Panel2.Width div 20;
                    Wall.Brush.Color:=clRed;
                    Wall.Pen.Color:=clRed;
                    Wall.Visible:=true;
                  end;
              end;
    end;
    end.
    the code might be a little ugly I was In a hurry to make 2 monsters walk around....

    USAGE!!!!
    1. Click Draw
    2. Click Move

    Otherwise EXCEPTION...
    Attached Files Attached Files

  5. #5
    Hi,

    I've been trying to alter the above code, to move the Shape smoothly, instead of this grid jump mode , well I'm having a hell of a time ... since shape has TOP and LEFT value and I'm calculating using that...everything Is fine if I go down, an right...
    but the moment I go up or left.... it has problems :
    11111
    1X001
    10111

    1 is wall, 0 is nothing, X is the object, my problem is if I move UP or LEFT ... it calculates down to 1,1 the moment the TOP,LEFT first pixel line enters 1,1... is there a way to make this stop... only have 1,1 if I'm fully inside ... (sorry for the terrible explanation)

    Code:
    unit MainFrm;
    // idea is
    // I check available directions...
    // then I set a direction
    // when I would hit a wall...I check again available directions...
    // then I set a direction...
    // could be good
    interface
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
    type
      TMain = class(TForm)
        Panel1: TPanel;
        Panel2: TPanel;
        Button1: TButton;
        Button2: TButton;
        Timer1: TTimer;
        Edit1: TEdit;
        Edit2: TEdit;
        Edit3: TEdit;
        procedure DrawMap();
        procedure DrawMonster();
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure Timer1Timer(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
      TDirection = ( dUp, dDown , dLeft, dRight );
      TMonster = class
        strict private
          FName : string;
          FPositionX,FPositionY : integer;
          FVelocityX,FVelocityY : integer;
          FSetDir : TDirection;
          Direction : array of TDirection;
        published
          property Name : string
            read FName write FName;
          property PositionX : integer
            read FPositionX write FPositionX;
          property PositionY : integer
            read FPositionY write FPositionY;
          property SetDir : TDirection
            read FSetDir write FSetDir;
          property VelocityX : integer
            read FVelocityX write FVelocityX;
          property VelocityY : integer
            read FVelocityY write FVelocityY;
          constructor Create( Name : string ; PositionX,PositionY : integer ; SetDir : TDirection );
          procedure GetPossibleDirections();
          procedure OnMove;
      end;
    
    
    
      TMapLine = array [0..19] of Integer;
      TMap = array [0..14] of TMapLine;
    const
      Map: Array [0..14,0..19] of Integer = ((1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1),
                                             (1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,1),
                                             (1,0,1,1,1,0,1,1,1,0,0,1,1,0,0,0,0,1,0,1),
                                             (1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1),
                                             (1,0,1,0,1,0,1,0,0,0,0,1,1,1,1,1,1,1,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,1,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1),
                                             (1,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1),
                                             (1,0,1,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,1),
                                             (1,0,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,0,1),
                                             (1,0,1,0,1,0,1,1,1,0,1,1,0,0,1,0,1,0,0,1),
                                             (1,0,1,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,0,1),
                                             (1,0,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1),
                                             (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1));
    
    var
      Main: TMain;
      Monster : array of TMonster;
      // hacky way later fix
      AI : array of TShape;
    
    
    implementation
    {$R *.dfm}
    constructor TMonster.Create(Name: string ; PositionX,PositionY : integer; SetDir : TDirection);
    begin
      FName := Name;
      FPositionX := PositionX;
      FPositionY := PositionY;
      FSetDir := SetDir;
      FVelocityX := 0;
      FVelocityY := 0;
    end;
    procedure TMonster.GetPossibleDirections;
    begin
      // debug
      Main.Edit1.Text:='';
      // empty directions
      SetLength(Direction,0);
      // no wall found up, way is free
      if Map[PositionY-1,PositionX] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dUp;
          Main.Edit1.Text:=Main.Edit1.Text+' UP ';
        end;
      // no wall found down, way is free
      if Map[PositionY+1,PositionX] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dDown;
          Main.Edit1.Text:=Main.Edit1.Text+' DOWN ';
        end;
      // no wall found left , way is free
      if Map[PositionY,PositionX-1] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dLeft;
          Main.Edit1.Text:=Main.Edit1.Text+' LEFT ';
        end;
      // no wall found right , way is free
      if Map[PositionY,PositionX+1] <> 1 then
        begin
          SetLength(Direction,length(Direction)+1);
          Direction[High(Direction)]:=dRight;
          Main.Edit1.Text:=Main.Edit1.Text+' RIGHT ';
        end;
    end;
    procedure TMonster.OnMove;
    begin
      VelocityY := 0;
      VelocityX := 0;
      if SetDir = dUp then
        begin
          if Map[PositionY-1,PositionX] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dDown then
        begin
          if Map[PositionY+1,PositionX] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dRight then
        begin
          if Map[PositionY,PositionX+1] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
      if SetDir = dLeft then
        begin
          if Map[PositionY,PositionX-1] = 1 then
            begin
              GetPossibleDirections();
              SetDir := Direction[random(length(Direction))];
            end;
        end;
    
    if SetDir = dUp then
        VelocityY:=-2;
      if SetDir = dDown then
        VelocityY:=+2;
      if SetDir = dRight then
        VelocityX:=+2;
      if SetDir = dLeft then
        VelocityX:=-2;
    
    end;
    procedure TMain.Button1Click(Sender: TObject);
    var i : integer;
    begin
      DrawMap();
      // we do 2 monsters for now
      SetLength(Monster,1);
      SetLength(AI,1);
      Monster[0] := TMonster.Create('Robert',1,1,dDown);
    //  Monster[1] := TMonster.Create('Devid',3,1,dDown);
    //  Monster[2] := TMonster.Create('Antal',18,4,dLeft);
      for i := 0 to length(Monster)-1 do
        begin
          AI[i] := TShape.Create(self);
          AI[i].Parent:=Panel2;
          AI[i].Top:=Monster[i].PositionY*(Panel2.Height div 15);
          AI[i].Left:=Monster[i].PositionX*(Panel2.Width div 20);
          AI[i].Height:=Panel2.Height div 15;
          AI[i].Width:=Panel2.Width div 20;
          AI[i].Brush.Color:=clGreen;
          AI[i].Pen.Color:=clGreen;
          AI[i].Visible:=true;
        end;
    end;
    procedure TMain.Button2Click(Sender: TObject);
    var i : integer;
    begin
      Timer1.Enabled:=true;
      {for i := 0 to length(Monster)-1 do
        begin
          Monster[i].OnMove;
        end;
      DrawMonster(); }
    end;
    procedure TMain.DrawMonster;
    var i : integer;
    begin
      // re-draw monster at new position
      for i := 0 to length(Monster)-1 do
        begin
    //      AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
    //      AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
          AI[i].Top:=AI[i].Top+Monster[i].VelocityY;
          AI[i].Left:=AI[i].Left+Monster[i].VelocityX;
          Monster[i].PositionY:=AI[i].Top div (Panel2.Height div 15);
          Edit2.Text:=inttostr(Monster[i].PositionY);
          Monster[i].PositionX:=AI[i].Left div (panel2.Width div 20);
          Edit3.Text:=inttostr(Monster[i].PositionX);
        end;
    end;
    procedure TMain.Timer1Timer(Sender: TObject);
    var i : integer;
    begin
      for i := 0 to length(Monster)-1 do
        begin
          Monster[i].OnMove;
        end;
      DrawMonster();
    end;
    procedure TMain.DrawMap;
    var x,y: integer;
        Wall : TShape;
    begin
        for y := 0 to 14 do
          for x := 0 to 19 do
              begin
                if Map[y,x] = 1 then
                  begin
                    Wall := TShape.Create(self);
                    Wall.Parent:=Panel2;
                    Wall.Top:=y*(Panel2.Height div 15);
                    Wall.Left:=x*(Panel2.Width div 20);
                    Wall.Height:=Panel2.Height div 15;
                    Wall.Width:=Panel2.Width div 20;
                    Wall.Brush.Color:=clRed;
                    Wall.Pen.Color:=clRed;
                    Wall.Visible:=true;
                  end;
              end;
    end;
    end.
    I've altered so there is only one moving monster.

    Please help

    Greetings
    Robert

  6. #6
    PGDCE Developer Carver413's Avatar
    Join Date
    Jun 2010
    Location
    Spokane,WA,Usa
    Posts
    206
    it is generally better to use a case statement for TDirection to prevent more then one block of code from executing in a single pass.
    also you should build a table of directions in a counter clock list either 4 or 8 directions. now there is no up or down only left or right foward or back.
    use an index var to set the direction add to location to move forward, subtract to move back. change index var to turn left or right.
    now you can move the monster in the same direction until it hits a wall then chose to go left or right.
    this is a link for a hex map there is some good stuff in there that could help you in your own design
    http://www.redblobgames.com/grids/hexagons/

  7. #7
    Hi,

    thank you for trying, but that confuses me only, and it is not the question I've asked...
    the Monster is moving inside the "labyrinth" automatically...


    my problem is the grid based collision is only working correctly if I move from grid to grid... if I move smoothly it does not
    example :

    wall is (pixels)
    0,0 - 31,31 (wall)
    32,32 - 63,63 (air)

    according to my logic the top,left corner of the monster square enters 63,63 , then it is divided down to array position of 1,1
    logic checks 1,1 top tile free or not ? -1 = wall! ( is correct... )
    movement stops at 63,63 because we hit wall...

    what I would like to solve is it should result in 1,1 if I get all the way down to 32,32 .... if it would div by 32 here now
    check for wall... and turn in another possible direction I would have "perfect" collision....


    Greetings
    Robert

    ps.: when I get back to work... I'll think about this a bit more, problem is there are many tutorials online, but most of em not work, or are
    way to complicated to grasp... I for starters don't need a fully fledged platform game sample which is beyond my comprehension for now

  8. #8
    Hello,

    After reading a few tile based collision detection tutorials with smooth scrolling enabled...I realized , that I need to check collisions based on direction I'm traveling in ( this might not work if I go both X,Y at same time, but for now I don't really care, one step at a time and not get overwhelmed)

    So right now I kinda hacked my own code... which is ugly
    Code:
    procedure TMain.DrawMonster;
    var i : integer;
    begin
      // re-draw monster at new position
      for i := 0 to length(Monster)-1 do
        begin
    //      AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
    //      AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
          AI[i].Top:=AI[i].Top+Monster[i].VelocityY;
          AI[i].Left:=AI[i].Left+Monster[i].VelocityX;
          if (Monster[i].SetDir = dDown) or (Monster[i].SetDir = DRight) then
            begin
              Monster[i].PositionY:=AI[i].Top div (Panel2.Height div 15);
              Edit2.Text:=inttostr(Monster[i].PositionY);
              Monster[i].PositionX:=AI[i].Left div (panel2.Width div 20);
              Edit3.Text:=inttostr(Monster[i].PositionX);
            end;
    
          if (Monster[i].SetDir = dUp) or (Monster[i].SetDir = DLeft) then
            begin
              Monster[i].PositionY:=(AI[i].Top+(AI[i].Height-1)) div (Panel2.Height div 15);
              Edit2.Text:=inttostr(Monster[i].PositionY);
              Monster[i].PositionX:=(AI[i].Left+(AI[i].Width-1)) div (panel2.Width div 20);
              Edit3.Text:=inttostr(Monster[i].PositionX);
            end;
    
    
        end;
    My mistake up till now, and with all my previous attempts as well was this
    when I calculated bottom and right side I simply did LEFT+WIDTH or TOP+WIDTH...which
    in the case of a 32x32 tile
    which has top,left 1,1
    resulted in a bottom,right 33,33 I know it seems so stupid now, but somehow I did not see it...

    that Is why now I add to left and top , height-1 and width-1...so I
    get 1,1 and 32,32...

    I tried it and it works...on the far right corner of the level there is no perfect touch on my monitor 1920x1080, but that is probably because (or 99% sure) my tile sizes are not n2...

    so I guess I did It...this will work nicely at 640x480 with 32x32 tiles...

    I'll modify my class in the upcoming days..to handle this calculation inside it...and not like this

    Greetings
    Robert

  9. #9
    Hi!
    I would suggest that you start unsig node based approach whend dealing with movment or pathfinding. Nodes are placed in the center of your map cells. Each node contains information about to which other node you can travel from it and movment cost for such movment.
    You also handle your units position based on ther center position instead of their TopLeft corner.
    So when you are moving your units from one point to another you are simply moving their center position from one map node to another.

    Advantages:
    - since you store posible movment in node connections you no longer need to always go and check all nearby tiles to figure out if you can move there or not
    - becouse you store each posible movment (node connections) for each node you can easily implement one way movment (moving from node A to node B is posible, but moving from node B to anode A is not posible)
    - you can have node connection between any two nodes. This alows you to implement things like portals with ease.
    - each node connection also stores the movment cost/movment speed so you can easily implement the ability to move faster when going downhill vercus goung uphill.
    - most popular pathfinding algorithms bases on node systems so you are already one step closer in implementing any of them.
    - by storing aditional value to each node you can easily determine whether a multitile sized unit can move to that tile or not (http://aigamedev.com/open/tutorial/c...d-pathfinding/)

    Disadvantages:
    - you need to generate and store node graph which could take some time and increases memory consumption
    - if using dynamic map loading (chunk based maps) you need to also dynamically update this node graph upon loading and unloading of each chunk.

    If you want I can make you a quick and dirty example of this but you will have to wait athleast till tomorow as I need to go to bed now in order to wake up early enough to go to work tomorow.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •