Skip to content

Book-Esenthel-2D-Basics

Khi game có rất nhiều đối tượng cùng dạng dữ liệu mà khó quản lý như nhiều enemy thì chúng ta có thể dùng container . Trong Esenthelfunction Memc để khai báo container.

Created date: 2022-02-27

1 Containers-Esenthel

Cú pháp khai báo như sau:

Memc<Type> variables

Sử dụng elms() để đếm số variables có trong container

Bây giờ sẽ thử tạo 10 circles có vị trí random trên màn hình

// Tạo các global variable
Memc<Circle> myCircles;


/******************************************************************************/
bool Init() // initialize after engine is ready
{
   // Tại function Init() này có thể khai báo dữ liệu ban đầu của các variables
   // Tạo 10 circles cho container myCircles
   for(int i=0; i<10; i++)
   {
      // SỬ dụng function class New() để add variables mới vài vị trí cuối của container
      myCircles.New().set(Random.f(0.05, 0.15), // function set thì có từ Circle
                          Vec2(Random.f(-D.w(), D.w()), Random.f(-D.h(), D.h())));

   }
   return true;
}
/******************************************************************************/
void Shut() // shut down at exit
{
}
/******************************************************************************/
// Update Game: Các animation trong game cần liên tục update
bool Update() // main updating
{
   if(Kb.bp(KB_ESC))return false; // exit if escape on the keyboard pressed


   return true;                   // continue
}
/******************************************************************************/
void Draw() // main drawing
{
   D.clear(BLACK); // Random background color
   for(int i=0; i<myCircles.elms(); i++)
   {
      myCircles[i].draw(RED);
   }

}

Để các Circle này xuất hiện lần lượt thì cần sử dụng thêm 1 hàm if

void Shut() // shut down at exit
{
}
/******************************************************************************/
// Update Game: Các animation trong game cần liên tục update
bool Update() // main updating
{
   if(Kb.bp(KB_ESC))return false; // exit if escape on the keyboard pressed
   if(Kb.bp(KB_SPACE))
   {
      myCircles.New().set(Random.f(0.05, 0.15), // function set thì có từ Circle
                          Vec2(Random.f(-D.w(), D.w()), Random.f(-D.h(), D.h())));
   }

   return true;                   // continue
}
/******************************************************************************/
void Draw() // main drawing
{
   D.clear(BLACK); // Random background color
   for(int i=0; i<5; i++)
   {
      myCircles[i].draw(RED);
   }
   for(int i=0; i<myCircles.elms()-5; i++)
   {
      myCircles[i].draw(BLUE);
   }

}

Ngoài function New() thì cũng có remove(). Function này sẽ rất có ích trong trường hợp có collision, các enemy trong container sẽ biến mất nếu va chạm với nhân vật (hoặc bị tiêu diệt) . Nếu muốn xoá hết thì dùng clear()

Challenge: Tạo game đặt bom, nhân vật là Circle BLUE, có khả năng thải ra các quả bom là các Rect BLUE. Enemy là các Circle RED xuất hiện random trên màn hình mỗi 2 giây. Các quả bơm có khả năng nổ dọc màn hình.
Cần di chuyển Circle BLUE để nổ hết các Circle RED

Hướng giải quyết: 1 Tạo myCircle là nhân vật trong game, có khả năng di chuyển. 2 Dùng Memc<Rect> để tạo các myRectBoom. Ấn Space sẽ tạo ra quả Boom. 3 Dùng Memc<Circle> để tạo nhiều enemyCircles, khi nhân vật chạm vào Enemy sẽ Game Over.

bool Update() // main updating
{
   if(Kb.bp(KB_ESC))return false; // exit if escape on the keyboard pressed

   myCircle.r = 0.08; // kích thước Circle

   if (Kb.b(KB_W)) myCircle.pos.y += Time.d(); // Di chuyển Circle dựa theo WASD
   if (Kb.b(KB_S)) myCircle.pos.y -= Time.d();
   if (Kb.b(KB_A)) myCircle.pos.x -= Time.d();
   if (Kb.b(KB_D)) myCircle.pos.x += Time.d();



   // Khi Circle chạm phải viền màn hình thì sẽ đi về vị trí ban đầu
   if (myCircle.pos.x > D.w()) myCircle.pos.x= -D.w();
   if (myCircle.pos.x < -D.w()) myCircle.pos.x= D.w();
   if (myCircle.pos.y > D.h()) myCircle.pos.y= -D.h();
   if (myCircle.pos.y < -D.h()) myCircle.pos.y= D.h();

   if (Kb.b(KB_SPACE)) // Thải boom bằng phím Space
   {
      myBoom.set(myCircle.pos.x-0.07, -D.h(), myCircle.pos.x+0.07, D.h());
   }

   for(int i=0; i<enemyCircles.elms(); i++)
   {
      if(Cuts(enemyCircles[i], myBoom)) enemyCircles.remove(i);
   }
   return true;                   // continue
}
/******************************************************************************/
void Draw() // main drawing
{
   D.clear(BLACK); // Random background color
   for(int i=0; i<enemyCircles.elms(); i++) // vẽ Enemy Red
   {
      enemyCircles[i].draw(RED);
   }

   myCircle.draw(BLUE); // Draw character
   myBoom.draw(YELLOW, false);

   D.text(-1, 0.95, S+"My Circle Position: "+myCircle.pos);
}
/******************************************************************************/

Kết quả như sau

Còn một số nội dung như Enemy xuất hiện theo thời gian thì practice ở các bài khác, vì tôi đang rất phấn khởi với các kiến thức mới.

2 Class-Esenthe

Khi các objects hoặc variables cung cấp thông tin đến cùng một nội dung như HP, mana, skill đều là variables của Player thì tạo class player với các functionsHP, mana...

2.1 Functions of class

Vì trong các functions có sẵn của66 Esenthel hay standard library của C++ không có các chức năng cụ thể mà chúng ta cần, vì vậy tuỳ trường hợp mà tạo các functions cho các class khác nhau. Tiếp tục trong trường hợp của game đặt boom, các enemy RED chỉ xuất hiện random và đứng im tại 1 vị trí. Để cải thiện game này thì enemy cần phải có khả năng di chuyển ngang màn hình vói tốc độ Random.

Bây giờ hãy tạo một class movingCircle có các function như reset position, moving along x axis. Sau đó dùng Container Memc dể tạo các enemy theo class vừa được tạo (nó sẽ tương đương với Memc<Circle> names nhưng sẽ có thể áp dụng được nhiều trường hợp khác)

2.2 Gán dữ liệu từ argument của function sang local variables

Ví dụ trong handmade class movingCircle có tạo các local variables như speed color là các variables có thuộc tính từ class có sẵn của Esenthel. Trong void create() function có chứa các arguments như speedcolor, sau đó sử dụng this hoặc T. để gán dữ liệu từ argument sang local variables và sử dụng được cho các class function khác trong class movingCircle. Nếu không dùng this thì các variables này không thể truy cập được trong các class khác. Chính vì vậy trong C++, recommend sử dụng cùng tên của local variablesarguments variables

class movingCircle
{
   Circle shapeCircle;
   Vec2 speed;
   Color color;


   void create(float radius,  Vec2 pos, Vec2 speed, Color color)// Create function with arguments for movingCircle
   {
      shapeCircle.r = radius; // .r là funtion có sẵn từ class Circle
      shapeCircle.pos = pos; // .pos là function có sẵn từ class Circle
      T.speed = speed; // this speed is equal to speed in local variable
      T.color = color; // sử dụng T. (tức là this) để gán dữ liệu từ argument sang local variables
   }

Có một vấn đề là không thể sử dụng được Cuts nữa vì trong Esenthel không có class movingCircle, chúng ta phải tự tạo một functions Cuts khác để đánh giá. Hoặc có thể dụng pointer cast nhưng tôi chưa hiểu rõ các function này nên tam dùng 1 biến trung gian. Kết quả như sau

Đây là class vừa tạo

class movingCircle
{
   Circle shapeCircle;
   Vec2 speed; // speed là vector di chuyển theo (x,y), khi nhân với delta time sẽ cho hiệu ứng moving
   Color color;


   void create(float radius,  Vec2 pos, Vec2 speed, Color color)// Create function with arguments for movingCircle
   {
      shapeCircle.r = radius; // .r là funtion có sẵn từ class Circle
      shapeCircle.pos = pos; // .pos là function có sẵn từ class Circle
      T.speed = speed; // this speed is equal to speed in local variable
      T.color = color; // sử dụng T. (tức là this) để gán dữ liệu từ argument sang local variables
   }

   // Tạo function move để check
   void move()
   {
      Vec2 newPos = shapeCircle.pos + speed * Time.d();
      shapeCircle.pos = newPos;

      if (shapeCircle.pos.x<-D.w()) shapeCircle.pos.x=D.w();
      if (shapeCircle.pos.x> D.w()) shapeCircle.pos.x=-D.w();
      if (shapeCircle.pos.y<-D.h()) shapeCircle.pos.y=D.h();
      if (shapeCircle.pos.y> D.w()) shapeCircle.pos.y=-D.h();
   }

   // Tạo function cho return là Vec2, toạ độ của shapeCircle
   Vec2 getPos()
   {
      return shapeCircle.pos;
   }

   // Function Get radius
   float getR()
   {
      return shapeCircle.r;
   }

   // Tạo function setPos, không cần trả kết quả mà tự làm mọi thứ bên trong function
   void setPos(Vec2 newPos)
   {
      shapeCircle.pos = newPos;
   }

   // Reset position function, các lệnh hoạt động bên trong function
   void reset() // void function để reset, không cần trả kết quả
   {
      shapeCircle.pos=0;
   }

   // Function draw không cần argument, các arguments đã có trong function 'create()'
   void draw()
   {
      shapeCircle.draw(color);
   }
}

Đây là content chính của game dựa trên class movingCircle

/******************************************************************************/
// Tạo các global variable
Circle         myCircle;
int speed; // tốc độ của myCircle
Memc<Rect>     myRectBooms;
Rect myBoom;
Rect myBoom2;
Memc<movingCircle> enemyCircles; // sử dụng handmade class movingCircle thay vì Circle

/******************************************************************************/
bool Init() // initialize after engine is ready
{   
   // Tạo 3 enemy từ ban đầu
   for (int i =0; i<5; i++)
   {
      movingCircle &enemyTmp =  enemyCircles.New(); // sử dụng Reference trước, sau đó mới được gán lệnh, nếu không Random chỉ dừng lại tại 1 vị trí

      enemyTmp.create(Random.f(0.05, 0.15), 
                                Vec2(Vec2(Random.f(-D.w(), D.w()), Random.f(-D.h(), D.h()))), // Random position
                                Vec2(Random.f(-1, 1), Random.f(-1, 1)), // random moving direction following x and y
                                Color(Random(256), Random(256), Random(256))// Có thể RED, BLUE.. nhưng Random cho thú vị
                                );

   }
   return true;
}
/******************************************************************************/
void Shut() // shut down at exit
{
}
/******************************************************************************/
// Update Game: Các animation trong game cần liên tục update
bool Update() // main updating
{
   if(Kb.bp(KB_ESC))return false; // exit if escape on the keyboard pressed

   myCircle.r = 0.08; // kích thước Circle

   if (Kb.b(KB_W)) myCircle.pos.y += Time.d(); // Di chuyển Circle dựa theo WASD
   if (Kb.b(KB_S)) myCircle.pos.y -= Time.d();
   if (Kb.b(KB_A)) myCircle.pos.x -= Time.d();
   if (Kb.b(KB_D)) myCircle.pos.x += Time.d();

   // Khi Circle chạm phải viền màn hình thì sẽ đi về vị trí ban đầu
   if (myCircle.pos.x > D.w()) myCircle.pos.x= -D.w();
   if (myCircle.pos.x < -D.w()) myCircle.pos.x= D.w();
   if (myCircle.pos.y > D.h()) myCircle.pos.y= -D.h();
   if (myCircle.pos.y < -D.h()) myCircle.pos.y= D.h();


   if (Kb.bp(KB_SPACE)) // Thải boom bằng phím Space, sẽ nổ thành dấu +, và tạo enemy
   {
      myBoom.set(myCircle.pos.x-0.07, -D.h(), myCircle.pos.x+0.07, D.h()); // Nổ trục dọc
      myBoom2.set(-D.w(), myCircle.pos.y-0.07, D.w(), myCircle.pos.y+0.07); // Nổ truc ngang 

      // Tạo 1 quả boom nhưng cũng tạo Enemy
      movingCircle &enemyTmp =  enemyCircles.New(); // sử dụng Reference trước, sau đó mới được gán lệnh, nếu không Random chỉ dừng lại tại 1 vị trí

      enemyTmp.create(Random.f(0.05, 0.15), 
                                Vec2(Vec2(Random.f(-D.w(), D.w()), Random.f(-D.h(), D.h()))), // Random position
                                Vec2(Random.f(-1, 1), Random.f(-1, 1)), // random moving direction following x and y
                                Color(Random(256), Random(256), Random(256))// Có thể RED, BLUE.. nhưng Random cho thú vị
                                );
   }

   // Cho enemy di chuyển random
   REPA(enemyCircles)// REPA tức là repeat all, lá một Macro của Esenthel, tương đương for i = 0 to number of Memc
   {
      enemyCircles[i].move();
   }

   Circle tempCircle; // tạo 1 Circle temporary để chuyển từ Class movingCircle thành Circle có sẵn trong Esenthel để sử dụng function Cuts.
   // mặc dù điều này sẽ làm chậm game và bộ nhớ, nhưng chưa biết rõ pointer và reference cũng như cast conversion...

   REPA(enemyCircles) // nếu Enemy chạm vào boom thì sẽ bị remove khỏi container. Nhược điểm là Boom k tự biến  mất sau 1s. Có thể dùng container để cải thiện!
   {
      tempCircle.set(enemyCircles[i].getR(), enemyCircles[i].getPos());
      if (Cuts(myBoom, tempCircle) || Cuts(myBoom2, tempCircle)) enemyCircles.remove(i);
   }



   return true;                   // continue
}
/******************************************************************************/
void Draw() // main drawing
{
   D.clear(BLACK); // Random background color
   for(int i=0; i<enemyCircles.elms(); i++) // vẽ Enemy Red
   {
      enemyCircles[i].draw();
   }

   myCircle.draw(WHITE); // Draw character
   myBoom.draw(YELLOW, false);
   myBoom2.draw(YELLOW, false);

   D.text(-1.1, 0.95, S+"My Circle Position: "+myCircle.pos);
   D.text(-1.1, 0.85, S+"Number of enemy: "+enemyCircles.elms());

}
/******************************************************************************/

Kết quả như sau:

Hiện tại game còn 1 vài nhược điểm đó là sự biến mất của quả boom sau 1s, dẫn đến tất cả enemyCircle đều biến mất khi chạm vào boom ngay khi được tạo thành.

2.3 Setters and Getters - thiết lập và tận dụng

Theo sách của MuteCode thì Class cần phải được thiết kế với các functions ngay từ ban đầu, dù cho có không sử dụng sau này. Hãy nghĩ về class trước, chứ không phải application.
Luật thứ 2 là không mix giữa logicinterface GUI network database trong một class.

–> Tóm lại mỗi type của variables cần phải có một class riêng.

Sử dụng gettersetter để phân biệt rõ ràng giữa class variableslocal variables. Thông thường sẽ sử dụng _variable để phân biệt với variable. Ví dụ như _pointclass variable

class score{
  int _point;

  int  point() {return _point;}
  void point( int value) {_point = value;}
}

Class trên sẽ tương đương với
class score {
  int points;

  int getPoints( ) { return points ; }
  void setPoints(int value) { points = value; }
}

Có một lưu ý là ; không được dùng tại cuối câu của class function

2.4 Private and public functions

Khi khai báo public thì các functions có thể được sử dụng bên ngoài class đó, ngược lại thì private chỉ được sử dụng trong class (không thể gọi ra được như Circle.pos.x)

// Class scores
class score
{
private:
   int points = 0;
   int counter = 0;
public:
   int getPoints()
   {
      return points;
   }

   score &setPoints (int points)
   {
      this->points = points; // sử dụng this-> tương đương với point trong private bằng points trong argument
      counter++;
      return T;
   }
}

Bây giờ thì không thể sử dụng Score.points nữa mà phải dùng Score.setPoints()

2.5 Class manager

Tương tự như các functions của một class, sẽ cùng thực hiện các phép tính có tính chất đóng góp cho môt đối tượng bên trong class đó. Nhiều class có chức năng tương tự có thể được quản lý bên trong một class mẹ gọi là Class manager

Ví dụ như sau:

1.Tạo một class liên quan đến Circle

class myCircle{
private:
  Circle c;
public:
  void update(){}
  void draw(){}

}

  1. Tạo một class manager
    class myCircleManager{
    private:
      Memc<myCircle> list;
    
    public:
      void createCircle{
        myCircle &temp = list.New();
      }
      void update(){
      REAP(list.elms()){
        list[i].update();
      }
      }
    }
    

Trong main.cpp sẽ dùng các class vừa tạo

myCircleManager MyCircleManager;

MyCircleManager.createCircle();

Tức là trong myCircleManager đã gọi một class khác là myCircle để có các chức năng. Điều này giống như chúng ta tạo một handmade class movingCircles, nó cũng sử dụng các class có sẵn như Circle có từ Esenthel.

2.6 Challenge game with Class

  • Game đặt boom như trên với một số cải thiện:
  • Các enemy có speed direction random -1 đến 1. Nếu chạm boundary thì phải đi ngược lại chứ không được đi xuyên màn hình

3 Reference-Esenthel

Tạo các variables trung gian trong tính toán thì sẽ tốn thời gian và dung lượng cho máy tính. Chính vì vậy sử dụng reference có thể loại bỏ được các vấn đề này.
Tóm lại, tránh các chức năng copies các variables.

3.1 Pass by reference

Thay vì copy toàn bộ giá trị của variable đến variables khác thì chỉ cần passing reference là được.

Ví dụ như tạo 1 function

Vec sumVec( Vec &pos1, Vec &pos2){
  return pos1 + pos2;
}

Trong trường hợp này thì Kết quả sẽ return một value

3.2 Return by reference

Thay vì trả về một value chúng ta cũng có thể trả về một reference tức là địa chỉ của kết quả đó. Nếu value thì máy tính vẫn cần phải tự tính toán và copy ra kết quả.

Tuy nhiên nếu return reference trong trường hợp này sẽ bị lỗi vì pos3 = sumVec(pos1, pos2) vẫn chưa tồn tại trong bộ nhớ, thì không thể có reference

Nguyên tắc chung của reference là chỉ được dùng nó khi đã create it nếu không sẽ bị lỗi.

4 Pointer

Pointer được tạo trước khi có reference. Nó phức tạp hơn so với reference, nhưng lại có nhiều chức năng hơn.

Trong khi reference không thể được tạo dựa trên một empty objects, pointer thì có thể chỉ đến bất cứ objects nào dù cho có tồn tại hay không, và có thể thay đổi giá trị về sau.

Thông thường thì reference được sử dụng an toàn và đơn giản hơn, dễ phát hiện các lỗi. Nhưng reference gặp khó khăn trong return a reference, đặt biệt là không có object được tạo (non existing object).

Tuy nhiên pointer cũng nên được point đến 1 reference nào đó hoặc là nullptr nếu không application dễ bị crash

4.1 Declare pointer vs reference

Cách khai báo pointerreferences như sau:

int j =43;
int& ref =  j; //Khi có dấu ampersand & thì C++ sẽ đọc adress. Tuy nhiên `ref` vẫn mang giá trị của `j`
int* ptr = &j; //Khi có dấu * thì `ptr` có giá trị là adress, 

  • ptr không mang giá trị của j.
  • Để sử dụng giá trị của i thì phải dùng dereference *ptr
  • Ngoài ra *ptr = j cũng bị lỗi, không như reference thì vẫn validref có thể dùng trực tiếp như value của j

4.2 Changing value by pointer

pointer có khả năng để thay đổi giá trị của 1 variable. Đây là một khả năng đặt biệt của pointer. Vì ý nghĩa của nó là con trỏ, nó chỉ vào vị trí của memory

int i = 42;
int* p = &i; //ampersand & để lấy adress của i

*p = 43; // Thêm * tức là dereference để lấy giá trị thực của p
// và nó sẽ làm thay đổi giá trị của i, vì memory adress tại i bây giờ là p, nhưng nó đã bị thay đổi nhò7 dereferencing

Như đoạn code trên đã thay đổi giá trị của pointer p từ 42 thành 43. Tức là i cũng thay đổi, vì i được lưu tại vị trí là p, khi giá trị của adress đó thay đổi thì i thay đổi theo.

4.3 No existing reference vs null Pointer

Như thảo luận phía trên thì pointer giúp khắc phụ lỗi của reference khi return non existing object. Bây giờ sẽ áp dụng lại class players

class players {
  Memc<Player> list; // tạo một container tên là list có Type là Circle, 

  Circle& add(Vec2& pos, Str& name){//Add nhân vật vào list với 2 arguments 
  //đều được khai báo có sử dụng adress, bao gồm Vec2 và Str. 
  //Và Kết quả trả về là reference
    Player& p = list.add(); // address của p thuộc dạng Player được add vào list,
    // tức là khi này list[i] đã được tạo
    p.set(pos, name); //Bây giờ p được gán các thuộc tính, 
    //tuy nhiên p không copy vào memory mà chỉ dựa theo adress của list[i]
    return p; // kết quả trả về là p, 
    //tuy nhiên khi khai báo function là &add thì kết quả sẽ là địa chỉ
  }
  // Sử dụng reference vẫn ok vì class add() đã tạo một object
  // Nên không bị reference đến non-existing object

  Circle* findByName(Str& name){ //Dùng để tìm tên của Circle
    FRPA(list){//
      if(Equal(list[i].getName(),name){//
        return &list[i]; // bắt buộc phải return ampersand& để lấy được adress
      } // tức là findByName* phải return một adress chứ không thể là 1 value
    }
  }
}

Cần chú ý kết quả return của pointer cần phải là một adress (sử dụng ampersand&)

Trong file main khai báo tương tự, chỉ khác ở phần pointer

players Players;

Players.add(Vec2(1,1), "niceGuy");
player* friend = Players.findByName("coolGuy");

//Check if the player exists
if(friend != null) Greet(friend);

Khi khai báo type là pointer *friend sử dụng variable Players đã có list niceGuy bên trong thì vẫn cho kết quả. Nhưng kết quả sẽ là return &list[i] = null, vì pointer chỉ vào non-exsisting object. Đây là khả năng của pointer so với reference.

4.4 Summary pointer and reference

Có nhiều trường hợp có thể sử dụng cả hai. Vậy thì nên sử dụng cái nào?
Lời Khuyên là sử dụng reference bất cứ khi nào có thể, còn pointer thì chỉ sử dụng trong trường hợp bất khả kháng.

Vì vậy chỉ sử dụng pointer khi trong các class mà có return là adress thì mới cần thiết.

Một số tổng hợp từ stackoverflowgeeksforgeeks

  1. Pointers can be NULL, references cannot be NULL.
  2. References are easier to use, const can be used for a reference when we don’t want to change value and just need a reference in a function.
  3. Pointer used with a * while references used with a &.
  4. Use pointers when pointer arithmetic operation are required.
  5. You can have pointers to a void type int a=5; void *p = &a; but cannot have a reference to a void type.
  6. A pointer can be re-assigned
    int a = 5;
    int b = 6;
    int *p;
    p =  &a;
    p = &b;
    
  7. Reference thí không thể re-assigned value

    int a = 5;
    int b = 6;
    int &p = a;
    int &p = b;  //At this line it will show error as "multiple declaration is not allowed".
    
    However it is valid statement,
    int &q=p;
    

  8. A pointer has its own memory address and size on the stack whereas a reference shares the same memory address (with the original variable) but also takes up some space on the stack.

  9. Indirection: You can have pointers to pointers offering extra levels of indirection. Whereas references only offer one level of indirection

    In Pointers,
    int a = 10;
    int *p;
    int **q;  //it is valid.
    p = &a;
    q = &p;
    
    Whereas in references,
    
    int &p = a;
    int &&q = p; //it is reference to reference, so it is an error.
    

  10. pointer có thể khai bái như int* i còn reference thì không thể mà phải là reference của object nào int& i = j tương đương với int* i = &j

  11. Khi gán giá trị cho reference thì dùng i = 42; thì máy tính sẽ tự động update giá trị cho j theo. Còn pointer thì phải dereference *i = 42;

Tóm lại

- Use references

-   In function parameters and **return types**.

- Use pointers:
- Use pointers if pointer arithmetic or passing NULL-pointer is needed. For example for arrays (Note that array access is implemented using pointer arithmetic).
- To implement data structures like linked list, tree, etc and their algorithms because to point different cell, we have to use the concept of pointers.

5 Enumeration

Còn được gọi tắt và enums có chức năng liệt kê số theo string. Enums có ích khi có nhiều sự kiện cùng một type nào đó. Ví dụ như enemy có thể là priest, warrior, hoặc rogue

Đọc đoạn code dưới đây để hiểu rõ hơn lợi ích của sử dụng enum

enum ENEMY_TYPE{
  ET_NONE,
  ET_WARRIOR,
  ET_ROGUE,
  ET_PRIEST,
}

class enemy{
  ENEMY_TYPE type= ET_NONE; // tạo một variable là type thuộc loại ENEMY_TYPE
  Rect r;

  void setType(ENEMY_TYPE type) {//argument là type
    T.type = type; //This type is equal to type in local variable
  }

  void draw(){
    switch(type){
      case ET_WARRIOR: Image(===drop image of warrior here===).draw(r); break;
      case ET_ROGUE: Image(===drop image of rogue here===).draw(r); break;
      case ET_PRIEST: Image(===drop image of priest here===).draw(r); break;
    }
  }
}

6 Application States

Hầu hết trong các games đều có các stages khác nhau như Intro, lobby, in game. Và các stages này không hoạt đồng cùng một lượt, mà lần lượt nhau hoạt động. Trong C++ có Application states để giúp tất cả các stages này hoạt động lần lượt chứ không cùng một lượt.

Giả sử chúng ta tạo một State tên là StateIntro nó sẽ bao gồm các functions khác nhau như sau

State StateIntro(UpdateIntro, DrawIntro, InitIntro, ShutIntro);

bool InitIntro() {return true;}
void ShutIntro() {}
bool UpdateIntro(){
  if(StateActive.time()>3 || Kb.bp(KB_ESC)){
    StateMenu.set(1.0);
    }
  return true;
  }
}
void DrawIntro(){
  D.clear(BLACK);
  D.text(0,0,"Intro");
}

Tương tự như vậy chúng ta sẽ có các states khác nhau như
Menu: State StateMenu(UpdateMenu, DrawMenu, InitMenu, ShutMenu);
In Game; State StateGame(UpdateGame, DrawGame, InitGame, ShutGame);

Mỗi state nên được tạo trong một file riêng. Trong file main.cpp thì lần lượt sử dụng chúng.

void InitPre(){
  INIT();
}

bool Init(){
  StateIntro.set(); // state này đang được kích hoạt nên state khác chưa được dùng
  return true;
}

void Shut(){}
bool Update() {return false;};
void Draw() {};

The following pages link to this page:



Created : Feb 27, 2022