7/07/2014

ProjectGame 1.4 - Nhảy và yếu tố Vật lý



Bây giờ, chúng ta sẽ làm cho nhân vật có thể nhảy bằng cách sử ụng Physics.Raycast của Unity. Chúng ta cũng có thể dùng OnCollisiọnEnter, OnCollisionExit hoặc OnCollisionStayfunctions để kiểm tra va chạm giữa nhân vật và sàn nhà, nhưng trong bài viết này chúng ta sẽ sử dụng Raycast bởi vì nó điều chỉnh linh hoạt hơn. Tuy nhiên, bởi vì Raycast chỉ là một đường thẳng không có bề dày, sẽ có lúc Raycast sẽ bỏ lở nếu địa hình quá mỏng. Vì vậy, chúng ta phải chắc chắn rằng địa hình chúng ta cần có độ dày ít nhất là 0.1 đơn vị.


Mục tiêu

1.1 - Tạo Camera và Level
1.2 - Tạo nhân vật 2D
1.3 - Tạo class (lớp) CharacterController và SpriteManager
>>1.4 - Nhảy và yếu tố vật lý 
1.5 - Tạo chìa khóa và mở cửa
1.6 - Chèn âm thanh và nút Replay


Bắt tay vào làm việc

B1. Double click vào file Js CharacterController_2D và chèn đoạn code sau vào phần khai báo ở đầu file script:

private var b_isJumping : boolean;
private var f_height : float;
private var f_lastY : float;
public var jumpSprite : JumpSpriteManager;
public var layerMask : LayerMask; //to check for the raycast

B2.Tiếp theo, chèn đoạn code sau vào phía trên cùng trong hàm Start( ):

//Get mesh from the character MeshFilter
mesh = GetComponent(MeshFilter).sharedMesh;
//Get hight from the top of our character to the bottom of our box collider
f_height = mesh.bounds.size.y* transform.localScale.y;

//Set up the last y-axis position of our character
f_lastY = transform.position.y;
b_isJumping = false;
        
B3. Qua hàm Update và chèn đoạn code được tô đỏ dưới đây vào:


        //If our character isn't jumping
        if (!b_isJumping) {
            if (Input.GetButton("Horizontal")) {
              //Walking
              in_direction = Input.GetAxis("Horizontal") < 0 ? -1: 1;
              rigidbody.velocity = new Vector3((in_direction*f_speed), rigidbody.velocity.y, 0);
              //Reset Stay animation frame back to the first frame
              loopSprites[0].resetFrame();

              //Update Walking animation while the character is walking
              loopSprites[1].updateAnimation(in_direction, renderer.material);
             
            } else {
              //Stay
              //Reset Walking animation frame back to the first frame
              loopSprites[1].resetFrame();
              //Update Stay animation while the character is not walking
              loopSprites[0].updateAnimation(in_direction, renderer.material);
               }
             
               if (Input.GetButton("Jump")) { //Jump
                b_isJumping = true;
                //Then make it Jump
                loopSprites[0].resetFrame();
                loopSprites[1].resetFrame();
                rigidbody.velocity = new Vector3(rigidbody.velocity.x, -Physics.gravity.y, 0);
            }
        } else {
            //update animation while it Jump
            jumpSprite.updateJumpAnimation(in_direction, rigidbody.velocity.y, renderer.material);
        }


B4. Bây giờ, chúng ta sẽ chèn đoạn code tô đỏ dưới đây vào hàm LateUpdate( ):

public function LateUpdate() : void {
        //Checking Jumping by using Raycast
        var hit : RaycastHit;
        var v3_hit : Vector3 = transform.TransformDirection (-Vector3.up) * (f_height * 0.5);
        var v3_right : Vector3 = new Vector3(transform.position.x + (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
        var v3_left : Vector3 = new Vector3(transform.position.x - (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
        if (Physics.Raycast (transform.position, v3_hit, hit, 2.5, layerMask.value)) {
            b_isJumping = false;
        } else if (Physics.Raycast (v3_right, v3_hit, hit, 2.5, layerMask.value)) {
            if (b_isJumping) {
            b_isJumping = false;
            }
        } else if (Physics.Raycast (v3_left, v3_hit, hit, 2.5, layerMask.value)) {
            if (b_isJumping) {
                b_isJumping = false;
            }
        } else {
            if (!b_isJumping) {
                if (Mathf.Floor(transform.position.y) == f_lastY) {
                    b_isJumping = false;
                } else {
                    b_isJumping = true;
                }
            }
        }
        f_lastY = Mathf.Floor(transform.position.y);

       //Update Main Camera
       Camera.main.transform.position = new Vector3(transform.position.x, transform.position.y, Camera.main.transform.position.z);
}

B5. Chúng ta sẽ tạo thêm hàm mới dùng để debug game, vì thế chúng ta sẽ không thể thấy nó trong quá trình chơi game. Hãy chèn đoạn code sau vào bên dưới hàm LateUpdate, bên trên class SpriteManager.

public function OnDrawGizmos() : void {
    mesh = GetComponent(MeshFilter).sharedMesh;
    f_height = mesh.bounds.size.y* transform.localScale.y;
    var v3_right : Vector3 = new Vector3(transform.position.x + (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
    var v3_left : Vector3 = new Vector3(transform.position.x - (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
    Gizmos.color = Color.red;
    Gizmos.DrawRay(transform.position, transform.TransformDirection (-Vector3.up) * (f_height * 0.5));
    Gizmos.DrawRay(v3_right, transform.TransformDirection (-Vector3.up) * (f_height * 0.5));
    Gizmos.DrawRay(v3_left, transform.TransformDirection (-Vector3.up) * (f_height * 0.5));
}

B6. Cuối cùng, chúng ta sẽ thêm một lớp quản lý các hình ảnh chuyển động khi nhảy của nhân vật, bởi vì động tác nhảy không cần lặp lại nên chúng ta phải tạo một lớp riêng với SpriteManager. Hãy chèn đoạn code sau vào bên dưới cùng của file Js.

 class JumpSpriteManager {
    public var t_jumpStartTexture : Texture2D; //Alternative Jump Texture play after t_jumpReadyTextures
    public var t_jumpAirTexture : Texture2D; //Alternative Jump Texture play when the player in the air at the top position of projectile
    public var t_jumpDownTexture : Texture2D; //Alternative Jump Texture play when the player fall to the ground
    public function updateJumpAnimation (_direction : int, _velocityY : float, _material : Material) : void {
        //Checking for the player position in the air
        if ((_velocityY>= -2.0) && (_velocityY<= 2.0)) { //Top of the projectile
            _material.mainTexture =t_jumpAirTexture;
        } else if (_velocityY> 2.0) { //Start Jump
            _material.mainTexture = t_jumpStartTexture;
        } else { //Fall
            _material.mainTexture = t_jumpDownTexture;
        }
        _material.mainTextureScale = new Vector2 (_direction * 1, 1);
        _material.mainTextureOffset = new Vector2 (_direction * 1, 1);
    }
}

B7. Chúng ta đã hoàn tất việc viết code, bây giờ trở lại Unity, tại thẻ Hierarchy, nhấp chọn Player và qua thẻ Inspector, chúng ta sẽ thấy Jump Sprite, tiến hành điều chỉnh như sau:


B8. Save scene lại và ấn nút Play để kiểm tra thành quả. Khi chơi bạn có thể ấn Space để nhân vật có thể nhảy lên.


Hoàn thành mục tiêu 1.4

Đầu tiên, chúng ta đã tạo kĩ năng nhảy cho nhân vật. b_isJumping sẽ kiểm tra nhân vật có đã nhảy lên hay không. f_height là chiều cao của nhân vật, được xử dụng để tính toán các yếu tố vật lý sau này. f_lastY là tọa độ mà nhân vật trên trục tọa độ Y ở thời điểm trước đó. jumpSprite là trình quản lý ảnh chuyển động giống lớp SpritẹumpManager, nó có khác biệt đôi chút với SpriteJumpManager bởi vì động tác nhảy không cần phải lặp lại liên tục, vì vậy chúng ta phải tạo một lớp mới để kiểm soát điều này.

Sau đó, ở phần đầu, chúng ta đã thêm dòng code để điều chỉnh và lấy thông tin mà chúng ta cần càng sớm càng tốt khi nhân vật chúng ta đã được tạo. Dòng code mesh = GetComponent(MeshFilter).sharedMesh; sẽ lấy thông tin của vật thể từ GameObject mà chúng ta đã gắn script CharacterController_2D vào, đó là Player. Kế tiếp, chúng ta lấy chiều cao của vật thể này bằng chỉ số scale của nó. Sau đó, chúng ta gán f_lastY bằng vị trí hiện tại của vật thể và điều chỉnh tham số b_isJumping thành false. Nếu nhân vật đang nhảy, nó sẽ cập nhật các ảnh động của động tác nhảy. Nếu nhân vật không nhảy, chương trình biên dịch sẽ nhảy đến dòng code cũ để kiểm tra nhân vật đang đi hay đứng yên. Chúng ta cũng đã thêm Input.GetButton("Jump"), để kiểm tra người chơi có ấn nút nhảy hay không; trong trường hợp này, nó sẽ thiết lập lại các ảnh chuyển động và thay đổi Y-velocity thành trọng lực âm. Điều này làm cho nhân vật chúng ta có thể nhảy sau khi người chơi ấn nút.

Chúng ta cũng đã thêm Physics.Raycast để kiểm tra việc di chuyển của nhân vật tốt hơn và ít lỗi chương trình hơn. Sau đó, chúng ta sử dụng hàm OnDrawGizmos( ) để xem và kiểm tra đường sáng - ray có ở đúng vị trí hay không. Chúng ta có thể dùng hàm này để kiểm tra hoặc debug cho game mà không cần phải xóa dòng này bởi vì nó không hiện ra trong game. Như chúng ta thấy trong hình bên dưới đây, các mũi tên màu đỏ biểu thị nơi của raycast, nhưng chúng ta không thực sự thấy chúng trong game.

Chúng ta đã tạo ra 3 tia ray:
Ray thứ nhất ở giữa thân nhân vật đến chân.
Ray thứ hai ở rất gần mép bên phải của vật thể. Chúng ta không vẽ ngay mép bởi vì chúng ta không muốn nhân vật tiến quá sát tường.
Ray thứ ba tương tự ray thứ hai nhưng nằm ở mép bên trái.

Kế tiếp, chúng ta có thể chắc rằng các tia ray không đụng bất kỳ thứ gì và nhân vật không nhảy. Chúng ta kiểm tra tọa độ Y trước đó của nhân vật xem nó có bằng với vị trí tọa độ Y hiện tại hay không. Nếu điều kiện này đúng, nghĩa là nhân vật đang đứng trên sàn, chúng ta thiết lập b_isJumping = false. Nếu điều kiện này sai, nghĩa là nhân vật chúng ta đagn rơi xuống và chúng ta phải thiết lập b_isJumping = true. Cuối cùng, chúng ta cập nhật lại vị trí tọa độ Y của nhân vật.

Hàm OnDrawGizmos cho phép hiển thị Physics Raycast trong thẻ Scene để giả lập cho chúng ta các tia ray hiển thị theo đúng hướng của nó. Trong hàm này chúng ta sẽ sử dụng Gizmos vẽ các tia để debug hoặc để điều chỉnh tọa độ các trục. Có thể tham khảo thêm về Gizmos tại địa chỉ web sau:

http://unity3d.com/support/documentation/ScriptReference/Gizmos.html

Cuối cùng, chúng ta tạo một lớp Sprite khác để hiển thị các ảnh của động tác Nhảy sẽ sử dụng bằng cách kiểm tra tọa độ Y của nhân vật. Trong JumpSpriteManager, chúng ta có mọi thứ tương tự như SpriteManager. Dù rằng chúng ta không cần phải lặp lại các ảnh của động tác Nhảy nhưng vẫn phải trả về ảnh của nhân vật khi động tác Nhảy kết thúc. Trước tiên, chúng ta phải thay đổi main texture thành t_jumpAirTexture, để biểu thị nhân vật đang nhảy trên không trung. Vì vậy, chúng ta cần kiểm tra tọa độ Y nằm trong khoảng -2 đến 2. Tiếp theo, chúng ta kiểm tra xem trục tọa độ Y có lớn hơn 2 không. Nghĩa là nhân vật chỉ mới bắt đầu nhảy, và rồi sau đó nhân vật sẽ rơi xuống. Cuối cùng, chúng ta cập nhật lại Tiling và Offset cho nhân vật.


Kiến thức bổ sung
Chúng ta còn vài thứ phải tìm hiểu trong bài viết này về Physics.Raycast

Physics.Raycast
Tại sao chúng ta phải điều chỉnh collider một chút bằng cách nhân 0.45 thay vì 0.5?

var hit : RaycastHit;
var v3_hit : Vector3 = transform.TransformDirection (Vector3.forward) * (f_height * 0.5);
var v3_right : Vector3 = new Vector3(transform.position.x + (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);

Từ dòng code if(Physics.Raycast (v3_right, v3_hit, hit, 2.5)), chúng ta sẽ thấy tia ray ở rìa bên phải sẽ được vẽ tính từ ngang thân nhân vật xuống 2.5 đơn vị. Hãy nhìn vào ảnh minh họa dưới đây.


Như đã thấy, các tia ray màu đỏ gần sát với box collider màu xanh một chút. Nếu chúng ta vẽ các tia này trùng với viền của box collider sẽ gây ra hiện tượng khi nhân vật đứng ở rìa của sàn sẽ lơ lửng trên không và không thể rơi xuống bởi vì các tia ray vẫn đang va chạm với sàn.


Điều cuối cùng, để hiển thị Gizmos trong khung nhìn Game, bạn chỉ cần click vào nút bên dưới đây.

4 comments:

  1. bạn ơi, project tạo xong mà ko thấy nhân vật chạy là sao nhỉ.;)

    ReplyDelete
  2. bạn cho mik xin bản copy của : CharacterController2D(js)
    dc không, mik viết nó bị lỗi, vs mik ko biết rõ về sripts nhiu lắm

    ReplyDelete
  3. : BCE0018: The name 'jumpSpriteManager' does not denote a valid type ('not found'). sai cho nao nhi

    ReplyDelete