2009-04-30 26 views
10

Tôi đã lang thang nếu có thể giả lập đối tượng Game để kiểm tra thành phần DrawableGameComponent của tôi?XNA giả lập đối tượng Game hoặc tách trò chơi của bạn

Tôi biết rằng các khuôn khổ giả mạo cần một giao diện để hoạt động, nhưng tôi cần giả lập đối tượng Game thực tế.

chỉnh sửa: Đây là một link để thảo luận tương ứng trên các diễn đàn Cộng đồng XNA. Bất kỳ trợ giúp nào?

+0

Chúc may mắn. Trên SlimDX, chúng tôi đang đánh giá một chuyển đổi bán buôn qua các giao diện để xử lý trường hợp sử dụng này. – Promit

+0

@Promit, Giao diện không phải là một viên đạn bạc ... trong một số trường hợp, một lớp trừu tượng là sự lựa chọn đúng đắn. Mặc dù giao diện chắc chắn hữu ích và chính xác trong các tình huống nhất định, nhưng nó không phải lúc nào cũng là công cụ thích hợp :-) –

+0

@Jelel Martinez - khi nói đến chế giễu/giả mạo, khi nào giao diện không phải là công cụ phù hợp? –

Trả lời

14

Có một số bài đăng hay trong diễn đàn đó về chủ đề kiểm tra đơn vị. Dưới đây là cách tiếp cận cá nhân của tôi để kiểm tra đơn vị trong XNA:

  • Bỏ qua Draw() phương pháp
  • Cô lập hành vi phức tạp trong phương thức lớp riêng bạn
  • thử nghiệm những thứ phức tạp, không đổ mồ hôi phần còn lại

Dưới đây là ví dụ về thử nghiệm để xác nhận rằng phương thức Cập nhật của tôi di chuyển Thực thể ở khoảng cách phù hợp giữa các cuộc gọi Update(). (Tôi đang sử dụng NUnit.) Tôi đã cắt bớt một vài dòng với các vector chuyển động khác nhau, nhưng bạn có ý tưởng: bạn không cần một Trò chơi để lái thử nghiệm của mình.

[TestFixture] 
public class EntityTest { 
    [Test] 
    public void testMovement() { 
     float speed = 1.0f; // units per second 
     float updateDuration = 1.0f; // seconds 
     Vector2 moveVector = new Vector2(0f, 1f); 
     Vector2 originalPosition = new Vector2(8f, 12f); 

     Entity entity = new Entity("testGuy"); 
     entity.NextStep = moveVector; 
     entity.Position = originalPosition; 
     entity.Speed = speed; 

     /*** Look ma, no Game! ***/ 
     entity.Update(updateDuration); 

     Vector2 moveVectorDirection = moveVector; 
     moveVectorDirection.Normalize(); 
     Vector2 expected = originalPosition + 
      (speed * updateDuration * moveVectorDirection); 

     float epsilon = 0.0001f; // using == on floats: bad idea 
     Assert.Less(Math.Abs(expected.X - entity.Position.X), epsilon); 
     Assert.Less(Math.Abs(expected.Y - entity.Position.Y), epsilon); 
    } 
} 

Edit: Một số lưu ý khác từ các ý kiến:

My Entity Lớp: tôi đã chọn để bọc tất cả trò chơi của tôi đối tượng lên trong một lớp Entity tập trung, mà trông giống như sau:

public class Entity { 
    public Vector2 Position { get; set; } 
    public Drawable Drawable { get; set; } 

    public void Update(double seconds) { 
     // Entity Update logic... 
     if (Drawable != null) { 
      Drawable.Update(seconds); 
     } 
    } 

    public void LoadContent(/* I forget the args */) { 
     // Entity LoadContent logic... 
     if (Drawable != null) { 
      Drawable.LoadContent(seconds); 
     } 
    } 
} 

Điều này mang lại cho tôi rất nhiều sự linh hoạt để làm cho các lớp con của thực thể (AIEntity, NonInteractiveEntity ...) có thể ghi đè lên Update(). Nó cũng cho phép tôi phân lớp Drawable một cách tự do, mà không có địa ngục của^2 lớp con như AnimatedSpriteAIEntity, ParticleEffectNonInteractiveEntityAnimatedSpriteNoninteractiveEntity. Thay vào đó, tôi có thể làm điều này:

Entity torch = new NonInteractiveEntity(); 
torch.Drawable = new AnimatedSpriteDrawable("Animations\litTorch"); 
SomeGameScreen.AddEntity(torch); 

// let's say you can load an enemy AI script like this 
Entity enemy = new AIEntity("AIScritps\hostile"); 
enemy.Drawable = new AnimatedSpriteDrawable("Animations\ogre"); 
SomeGameScreen.AddEntity(enemy); 

lớp drawable My: Tôi có một lớp trừu tượng mà từ đó tất cả các đối tượng vẽ của tôi đều có nguồn gốc. Tôi đã chọn một lớp trừu tượng vì một số hành vi sẽ được chia sẻ. Thay vào đó, hoàn toàn có thể chấp nhận để xác định đây là một số interface, nếu điều đó không đúng với mã của bạn.

public abstract class Drawable { 
    // my game is 2d, so I use a Point to draw... 
    public Point Coordinates { get; set; } 
    // But I usually store my game state in a Vector2, 
    // so I need a convenient way to convert. If this 
    // were an interface, I'd have to write this code everywhere 
    public void SetPosition(Vector2 value) { 
     Coordinates = new Point((int)value.X, (int)value.Y); 
    } 

    // This is overridden by subclasses like AnimatedSprite and ParticleEffect 
    public abstract void Draw(SpriteBatch spriteBatch, Rectangle visibleArea); 
} 

Các lớp con xác định logic Vẽ của riêng chúng. Trong ví dụ xe tăng của bạn, bạn có thể làm một vài điều:

  • Thêm một thực thể mới cho mỗi viên đạn
  • Tạo một lớp TankEntity trong đó xác định một danh sách, và ghi đè Vẽ() để lặp qua các Bullets (trong đó xác định một phương thức Draw của riêng mình)
  • thực hiện một ListDrawable

Dưới đây là một ví dụ về thực hiện ListDrawable, bỏ qua vấn đề làm thế nào để quản lý danh sách riêng của mình.

public class ListDrawable : Drawable { 
    private List<Drawable> Children; 
    // ... 
    public override void Draw(SpriteBatch spriteBatch, Rectangle visibleArea) { 
     if (Children == null) { 
      return; 
     } 

     foreach (Drawable child in children) { 
      child.Draw(spriteBatch, visibleArea); 
     } 
    } 
} 
+0

Bạn có thể chỉ cho tôi giao diện "Thực thể" của mình không? Nó là gì? Một thành phần? Một lớp tùy chỉnh? – drozzy

+0

Đó là một lớp tùy chỉnh - mọi đối tượng trò chơi tôi render đều sử dụng nó. Tôi đặc biệt tránh GameComponents cho mọi thứ trừ trình xử lý đầu vào của tôi và http://creators.xna.com/en-US/samples/gamestatemanagementManager ScreenManager. Nó định nghĩa các phương thức như Update (double time) để di chuyển tốc độ * theo thời gian của NextStep. – ojrac

+0

Để hỗ trợ nhiều loại drawables (sprites, sprites hoạt hình, hạt), Entity * của tôi chứa * một Drawable - một lớp trừu tượng xử lý các cuộc gọi Draw(). – ojrac

2

Bạn có thể sử dụng công cụ có tên TypeMock mà tôi tin rằng không yêu cầu bạn phải có giao diện. Phương pháp khác và thường được theo sau của bạn là tạo một lớp mới kế thừa từ Trò chơi và cũng thực hiện một giao diện mà bạn tạo phù hợp với đối tượng Game. Sau đó, bạn có thể mã chống lại giao diện đó và chuyển đối tượng trò chơi 'tùy chỉnh' của mình.

public class MyGameObject : Game, IGame 
{ 
    //you can leave this empty since you are inheriting from Game.  
} 

public IGame 
{ 
    public GameComponentCollection Components { get; set; } 
    public ContentManager Content { get; set; } 
    //etc... 
} 

Hơi tẻ nhạt, nhưng nó cho phép bạn đạt được tính nhại.

+0

Có cách nào tôi có thể kế thừa từ một thứ thay vì hai? Ngoài ra, những gì thừa hưởng từ IGame cho tôi? Nó có vẻ như không kế thừa từ nó thay đổi không có gì. Có lẽ tôi không rõ về điều này, xin lỗi. – drozzy

+0

điều này sẽ không hoạt động, bởi vì các nhà xây dựng để drawablegamecomponent nhu cầu một thể hiện của trò chơi, không IGame ;-) –

3

các khung như MOQRhino Mocks không cần một giao diện cụ thể. Họ có thể giả lập bất kỳ lớp học không niêm phong và/hoặc trừu tượng là tốt. Trò chơi là một lớp trừu tượng, vì vậy bạn không nên gặp khó khăn khi chế nhạo nó :-)

Một điều cần lưu ý với ít nhất hai khung đó là đặt bất kỳ kỳ vọng nào về phương pháp hoặc thuộc tính, chúng phải là ảo hoặc trừu tượng. Lý do cho điều này là trường hợp giả tạo nó tạo ra cần phải có khả năng ghi đè lên. Các typemock được đề cập bởi IAmCodeMonkey Tôi tin rằng có một cách xung quanh này, nhưng tôi không nghĩ rằng typemock là miễn phí, trong khi hai tôi đã đề cập được.

Là một sang một bên, bạn cũng có thể kiểm tra một dự án của tôi có thể giúp trong việc tạo ra các unit test cho game XNA mà không cần phải làm mocks: http://scurvytest.codeplex.com/

+0

Cảm ơn câu trả lời. Xin lỗi nhưng tôi không thực sự có nhiều thời gian để gỡ lỗi một khung công tác khác. Có vẻ thú vị. – drozzy

+0

Tôi bị cám dỗ để từ bỏ việc này. Chỉ lo ngại đặt một yếu tố không chắc chắn (mới đến XNA, mới đối với TDD) trong quá trình này. Bất cứ ai có bất kỳ XP với nó? –

+0

@boris callens: Tôi đã sử dụng Rhino Mocks rất nhiều. Nó hoạt động rất tốt. – Skurmedel

3

Bạn không cần phải thử nó. Tại sao không tạo ra một đối tượng trò chơi giả mạo?

Kế thừa từ Trò chơi và ghi đè các phương pháp bạn dự định sử dụng trong các bài kiểm tra để trả về giá trị đóng hộp hoặc tính toán phím tắt cho bất kỳ phương pháp/thuộc tính nào bạn cần. Sau đó vượt qua giả để thử nghiệm của bạn.

Trước khi chế nhạo khuôn khổ, mọi người cuộn mocks/sơ đồ/hàng giả của riêng mình - có thể nó không phải là nhanh chóng và dễ dàng, nhưng bạn vẫn có thể.

+0

Tôi thích nó. Bằng cách nào đó cơn lốc của những khuôn khổ này đã làm mờ đi con đường của tôi. Tôi sẽ thử lại và lấy lại cho bạn ở đây với kết quả! – drozzy

+0

Âm thanh tuyệt vời. Chúc may mắn! Tôi rất vui khi biết nó diễn ra như thế nào bởi vì tôi có một dự án trò chơi được thực hiện 1/4 của chính tôi đang ngồi trên bếp sau. –

+0

Sau khi nhìn vào điều này nhiều hơn, thật đáng buồn tôi không thể hình dung một cách dễ dàng để làm điều này. Trừ khi tôi giả mạo một loạt các phương pháp. Tôi cũng không chắc chắn về tác dụng phụ của nó. Cảm ơn mặc dù. – drozzy

0

Để bắt đầu một điểm giống như thế này, tôi sẽ nhấn XNA WinForms Sample. Sử dụng mẫu này như là một mô hình, nó xuất hiện rằng một cách để hình dung các thành phần trong WinForm là tạo một điều khiển cho nó theo cùng một kiểu với SpinningTriangleControl từ mẫu. Điều này chứng tỏ làm thế nào để render mã XNA mà không cần một cá thể Game. Trò chơi thực sự không quan trọng, nó thực sự quan trọng đối với bạn. Vì vậy, những gì bạn sẽ làm là tạo một dự án Library có logic Load/Draw của Component trong một lớp và trong các dự án khác của bạn, tạo một lớp Control và một lớp Component là các trình bao bọc cho mã thư viện trong các môi trường tương ứng của chúng. Bằng cách này, mã thử nghiệm của bạn không bị trùng lặp và bạn không phải lo lắng về việc viết mã sẽ luôn khả thi trong hai trường hợp khác nhau.

+0

Xin lỗi nhưng không đọc mẫu đó, câu trả lời của bạn không có ý nghĩa gì nhiều. Tôi sẽ kiểm tra nó ở nhà sau. – drozzy

+0

Vâng ý chính của ý tưởng là bạn giữ logic cho kết xuất của bạn trong một lớp riêng biệt và bạn viết 2 lớp về cơ bản là vỏ cho logic kết xuất trong môi trường tương ứng của chúng - GameComponent và WinForms Control. Vì vậy, bạn không thực sự viết một Thành phần tạo ra một cá thể Trò chơi, bạn đang viết một Điều khiển không bao giờ sử dụng và chia sẻ việc triển khai giữa hai lớp trình bày. – Jeremy

1

Tôi sẽ heo con lại về bài đăng của bạn nếu bạn không nhớ từ mine có vẻ là ít hoạt động và bạn đã đưa đại diện của bạn trên dòng;)

Như tôi đã đọc qua bài viết của mình (cả ở đây & XNAForum) Tôi nghĩ rằng đó là cả hai khuôn khổ có thể được tiếp cận nhiều hơn và thiết kế của tôi (của chúng tôi) đó là không hoàn hảo.

Khung có thể được thiết kế để dễ mở rộng hơn. Tôi gặp khó khăn khi tin rằng đối số chính của Shawnperf hit on interfaces. Để trả lời tôi, số his colleague cho biết số lần truy cập hoàn toàn có thể dễ dàng bị né tránh.
Lưu ý rằng khuôn khổ đã có giao diện IUpdatable và IDrawable.Tại sao không đi tất cả các cách?

Mặt khác, tôi cũng nghĩ rằng thiết kế của tôi (và của bạn) không hoàn hảo. Tôi không phụ thuộc vào đối tượng Game, tôi phụ thuộc vào đối tượng GraphicsDevice rất nhiều. Tôi sẽ xem xét làm thế nào tôi có thể phá vỡ điều này. Nó sẽ làm cho mã phức tạp hơn, nhưng tôi nghĩ rằng tôi thực sự có thể phá vỡ những phụ thuộc đó.

+0

True dat. Ví dụ một thách thức tôi có là để vượt qua một loạt sprite từ một nhà xây dựng hoặc tạo ra một từ bên trong lớp. Việc truyền nó qua constructor làm cho đoạn mã được tách rời hơn, nhưng sau đó tôi phải đưa ra các giả định về nơi mà tôi gọi các lệnh gọi sprite_batch.begin() và end(). – drozzy

+0

Jeps, có cùng một thứ. Tôi đang chuyển nó cho nhà xây dựng bây giờ bởi vì tôi không muốn lớp của tôi biết nó trông như thế nào. Tôi nghĩ điều này khiến họ di chuyển sang các trò chơi khác. –

+0

Ngoài ra, bắt đầu() - ing và kết thúc() - ing chúng trong mỗi lớp tôi có thể tưởng tượng có thể gây ra một sự suy giảm đáng chú ý. –

Các vấn đề liên quan