LibGDX Tutorial 9: Scene2D
Trong bài viết này chúng ta sẽ tìm hiểu về thư viện Scene2D. Điều đầu tiên bạn cần biết là Scene2D là hoàn toàn tuỳ chọn, nếu bạn không muốn sử dụng nó cũng không sao. Tất cả những phần khác, trừ khi các bit được xây dựng trên Scene2D sẽ tiếp tục làm việc tốt. Vậy Scene2D là gì? Tóm lại, nó là ...
Trong bài viết này chúng ta sẽ tìm hiểu về thư viện Scene2D. Điều đầu tiên bạn cần biết là Scene2D là hoàn toàn tuỳ chọn, nếu bạn không muốn sử dụng nó cũng không sao. Tất cả những phần khác, trừ khi các bit được xây dựng trên Scene2D sẽ tiếp tục làm việc tốt.
Vậy Scene2D là gì? Tóm lại, nó là một scene graph 2D. Vậy, bạn có thể hỏi "Một scene graph là gì?". Câu hỏi hay! Bản chất một scene graph là một cấu trúc dữ liệu để lưu trữ thông tin trong game world của bạn. Vì vậy nếu game world của bạn bao gồm hàng chục hay hàng trăm sprites, những sprites sẽ được lưu trong scene graph. Ngoài lưu trữ nội dung game world của bạn, Scene2D cung cấp một số chức năng mà nó thực hiện trên dữ liệu đó. Những điều như hit detection, tạo ra hệ thống phân cấp giữa các game objects, định tuyến đầu vào, tạo các actions cho các thao tác một node theo thời gian,...
Bạn có thể nghĩ Scene2D là một framework level cao hơn để tạo ra những game hàng đầu sử dụng LibGDX.
Các đối tượng thiết kết của Scene2D được xây dựng ví như một màn biểu diện. Tại tầng trên cùng bạn có stage tức là sân khấu. Đây là nơi mà màn biểu diễn được diễn ra. Stage chứa một Viewport. Sự trừu tượng hoá tiếp theo là Actor, đó là những nghệ sĩ trên sân khấu. Cách đặt tên này có vẻ có một chút sai sót vì Actor không chỉ như là diễn viên trên sân khấu. Actor có thể cũng bao gồm ánh sáng, một phần cảnh trên sân khấu,... Về cơ bản các actor là những thứ tạo nên game của bạn. Vì vậy, về cơ bản bạn chia game vào các scene hợp lý (ví dụ như screen, stage, level, bất cứ cái gì có nghĩa) bao gồm các actor. Một lần nữa, nếu những thứ này không phù hợp với game của bạn, bạn không cần phải sử dụng Scene2D.
Vì vậy, đó là ý tưởng đằng sau việc thiết kế, chúng ta hãy xem xét một ví dụ thực tế hơn. Chúng ta chỉ đơn giản là sẽ tạo ra một scene với một stage duy nhất và thêm vào một actor duy nhất cho nó.
package com.thinhhung.game; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.utils.viewport.ScreenViewport; public class SceneSample implements ApplicationListener { public class MyActor extends Actor { Texture texture = new Texture(Gdx.files.internal("dante.png")); @Override public void draw(Batch batch, float parentAlpha) { batch.draw(texture, 0, 0); } } private Stage stage; @Override public void create () { stage = new Stage(new ScreenViewport()); MyActor myActor = new MyActor(); stage.addActor(myActor); } @Override public void resize(int awidth, int height) { } @Override public void render () { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); stage.draw(); } @Override public void pause() { } @Override public void resume() { } @Override public void dispose() { stage.dispose(); } }
Cũng lưu ý, tôi cũng thêm vào một hình ảnh nhân vật mà tôi đã sử dụng trong các ví dụ trước đó trong thư mục assets của android có tên là dante.png. Xem các tutorial trước nếu bạn không biết làm thế nào để làm được điều này. Khi bạn chạy ứng dụng bạn sẽ thấy như sau:
Như bạn có thể nhìn thấy nó là quá trình khá đơn giản làm việc với Stage2D. Chúng tôi tạo ra một Actor có nguồn gốc từ class có tên là MyActor. MyActor đơn giản tải một texture từ file. Thành phần chính là phương thức draw(). Phương thức này sẽ được gọi tại mỗi frame bởi các stage có chứa actor. Ở đây bạn sẽ draw actor để stage sử dụng cung cấp Batch. Batch là interface của class SpriteBatch mà chúng ta đã thấy ở các ví dụ trước đó và chịu trách nhiệm gửi các lệnh vẽ tới OpenGL. Trong ví dụ này, chúng ta chỉ cần vẽ Texture của chúng ta bằng batch tại điểm 0, 0. Actor của bạn có thể dễ dàng được lập trình ra từ một spritesheet,... Một trong những điều tôi phải chỉ ra ở đây, ví dù này rất ngắn gọn, trong thức bạn sẽ sẽ muốn quản lý những thứ khác nhau, mỗi MyActor sẽ bị rò rỉ khi Texture của nó bị destroy.
Trong ứng dụng của chúng ta, phương thức create() tạo ra stage với tham số new ScreenViewPort() nó sẽ tạo ra một stage với view port fill cả ứng dụng. Khi stage được tạo ra, chúng ta tạo ra một thể hiện của MyActor và thêm nó vào stage bằng cách gọi hàm stage.addActor(). Tiếp theo trong phương thức render(), chúng ta clear màn hình và draw stage bằng cách gọi phương thức draw(). Việc này sẽ gọi lần lượt các phương thức draw() của mỗi actor mà stage đã chứa. Cuối cùng, bạn có thể nhận thấy rằng chúng ta cần dispose stage khi ứng dụng gọi phương thức dispose() để ngăn chặn rò rỉ bộ nhớ.
Như vậy chúng ta đã biết làm thế nào để tạo một ứng dụng đơn giản sử dụng Scene2D. Tuy nhiên, một điều mà tôi chưa động đến là thực sự diễn viên phải làm gì đó hoặc là làm thế nào bạn có thể điều khiển nó. Chúng ta hãy nhìn vào ví dụ tiếp theo, hãy chú ý những thay đổi trong code:
package com.thinhhung.game; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.utils.viewport.ScreenViewport; public class SceneSample implements ApplicationListener { public class MyActor extends Actor { Texture texture = new Texture(Gdx.files.internal("dante.png")); float actorX = 0; float actorY = 0; public boolean started = false; public MyActor () { setBounds(actorX, actorY, texture.getWidth(), texture.getHeight()); addListener(new InputListener() { @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { ((MyActor) event.getTarget()).started = true; return true; } }); } @Override public void draw(Batch batch, float parentAlpha) { batch.draw(texture, actorX, actorY); } @Override public void act(float delta) { if (started) { actorX += 5; } } } private Stage stage; @Override public void create () { stage = new Stage(new ScreenViewport()); Gdx.input.setInputProcessor(stage); MyActor myActor = new MyActor(); myActor.setTouchable(Touchable.enabled); stage.addActor(myActor); } @Override public void resize(int awidth, int height) { } @Override public void render () { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); stage.act(Gdx.graphics.getDeltaTime()); stage.draw(); } @Override public void pause() { } @Override public void resume() { } @Override public void dispose() { stage.dispose(); } }
Chạy ứng dụng lên, click vào sprite nhân vậtdante và nó sẽ chạy. Chúng ta sẽ xem xét kỹ hơn về các đoạn code ngay bây giờ.
Hãy bắt đầu những thay đổi chúng ta đã làm với class MyActor. Thay đổi rõ ràng nhất mà bạn sẽ thấy là việc thêm vào một constructor. Tôi làm điều này để thêm vào một event listener vào các actor của chúng tam, nó truyền vào một đối tượng khởi tạo từ class InputEvent như một tham số, nó có phương thức getTarget() sẽ trả về Actor đã được touch hoặc click vào. Chúng ta sẽ dễ dàng ép kiểu sang một đối tượng MyActor và xét thuộc tính started thành true. Một điều quan trọng khác mà bạn có thể nhận thấy là phương thức setBounds() được gọi, cái này rất quan trọng, nếu bạn kế thừa từ một Actor, bạn cần thiết lập các giới hạn bounds hoặc nó sẽ không thể được click hay touch. Đơn giản chỉ cần thiết lập các giới hạn để phù hợp với kết cấu của actor.
Khác với constructor, thay đổi lớn khác mà chúng tôi đã làm với MyActor là thêm phương thức act(). Chỉ giống như với draw(), phương thức act() được gọi trên mỗi actor trên stagel. Trong trường hợp này, chúng ta chỉ cần thêm 5 pixels tới toạ độ X của MyActor mỗi frame. Tất nhiên, chúng ta chỉ làm điều này khi thuộc tính started đã được thiết lập true.
Trong phương thức create(), chúng ta cũng có hai sự thay đổi nhỏ. Đầu tiên chúng ta đăng ký một InputProcessor. Chỉ có một stage, vì vậy chúng ta chỉ cần truyền stage vào phương thức setInputProcessor(). Như bạn đã thấy ở trên, stage gọi InputListeners của tất cả các actor con. Chúng ta cũng thiết lập cho các actor có thể click hoặc touch được myActor.setTouchable(Touchable.enabled);, mặc dù tôi tin nó là mặc định đã được thiết lập. Chỉ có một sự thay đổi khác trong phương thức render(), chúng ta gọi stage.act() và truyền vào thời gian đã qua kể từ frame trước đó. Phương thức này cũng gọi toàn bộ những phương thức act() của những actor trong stage.
Scene2D là một chủ đề khá lớn, vì vậy tôi sẽ phải làm việc với nó qua các tutorial sau nữa.
Bạn có có thể tham khảo source code của project này tại đây.