C++ Programming Tutorials_4翻譯



所在原文目錄結構位置:

C++ Programming Guide

 |_____Programming Quick Start Guide

 |_____Introduction to C++ Programming in UE4

 |_____C++ Programming Tutorials

 |_____Managing Game Code

 |_____Development Setup

 |_____Gameplay Programming

 |_____Engine Architecture

 |_____Console Manager: Console Variables in C++

 |_____Command-Line Arguments

 |_____Assertions

 |_____Blueprint Function Libraries

 |_____Unreal Build System

 |_____Plugins

 |_____Coding Standard

 |_____Symbol Debugger


原文地址:Player-Controlled Cameras


-------------------------------------------------------------------------------------------------------------------------------------------------------


C++ Programming Tutorials

C++編程教程

1.Player Input and Pawns 玩家輸入和Pawn(個人感覺Pawn是副角色,配角) 

 見譯文:  C++ Programming Tutorials_1 


2.Game-Controlled Cameras 遊戲控制攝像機

見譯文:C++ Programming Tutorials 2


3.Variables, Timers, and Events 變量,定時器和事件

見譯文:C++ Programming Tutorials 3

4.Player-Controlled Cameras 玩家控制攝像機

This tutorial will show you how to activate a camera, and change your active camera from one to another.

本教程展示如何激活一個攝像機,和在攝像機之間切換激活.


   1.Attach A Camera To A Pawn  綁定一個攝像機到Pawn

           !!!If you are new to Unreal Engine 4, you might want to read ourProgramming Quick Start tutorial first. For this tutorial, we will assume you are familiar with creating a project, adding C++ code to it, compiling your code, and adding Components to Actors in the Unreal Engine editor.

           !!!如果你是新手,請先翻閱前面對應的譯文.

           ①We will begin by creating a new, Basic Code project, with starter content, named "HowTo_PlayerCamera". We'll want to create a custom Pawn class, so let's do that first. For this tutorial, we'll use the name "PawnWithCamera" for our new Pawn class.

            我們先從新創建一個新的,C++基礎代碼,具有初學者內容的項目開始,項目命名爲"HowTo_PlayerCamera".我們要創建一個Pawn 類,在本教程,我們用PawnWithCamera"來命名我們新建的Pawn 類.


           ②Next, in Visual Studio, we should openPawnWithCamera.h and add the following code to the bottom of our class definition:

            接下來,在VS,我們打開PawnWithCamera.h,添加如下代碼到類定義的最後:

  

protected:
    UPROPERTY(EditAnywhere)
    USpringArmComponent* OurCameraSpringArm;
    UCameraComponent* OurCamera;

      We will use these variables to create a SpringArmComponent with a CameraComponent attached to the end. Spring arms are a good, simple way to attach cameras (or other Components) so that they're not overly rigid and feel more fluid as they move.

      我們將用這些變量來創建一個SpringArmComponent(減震杆組件),並附加一個CameraComponent.減震杆是好的,簡單的附加上攝像機(或者其他Components)以致於它們不會那麼僵化,移動起來更流暢一些

           ③After that, we actually need to create our Components in our constructor. Add the following code toPawnWithCamera.cpp inside of APawnWithCamera::APawnWithCamera:

           之後,我們實際上需要在構造函數中創建我們的Components ,添加如下代碼到PawnWithCamera.cpp ,APawnWithCamera::APawnWithCamera裏面:

//Create our components
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));//創建根組件
OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));//創建<span class="web-item">減震杆組件</span>
OurCameraSpringArm->AttachTo(RootComponent);//減震杆組件 附加 到根組件
OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));//設置減震杆的位置和旋轉
OurCameraSpringArm->TargetArmLength = 400.f;//杆長
OurCameraSpringArm->bEnableCameraLag = true;//相機延遲開啓
OurCameraSpringArm->CameraLagSpeed = 3.0f;//延遲速度3秒

            This creates a basic, empty SceneComponent as the root of our Component hierarchy, then creates and attaches a SpringArmComponent to it. The Spring Arm is then set at a default pitch of -60 degrees (that is, looking 60 degrees downward) and a position of 50 units above the root. We also establish a few values specific to the SpringArmComponent class that will determine its length and the smoothness of its motion. With that finished, we simply need to create and attach a CameraComponent to the socket on the end of the SpringArmComponent, as follows:

            這將創建一個基本的,空的SceneComponent ,作爲我們的組件層次結構的根,然後爲它創建和附加一個SpringArmComponent .Spring Arm 默認設置爲傾斜角-60°,相對於root 50單位的距離.我們也可以設定一些值來指定SpringArmComponent 類決定它的長度和平滑的運動,這部完成,我們只需要簡單地創建和附加一個CameraComponent到插槽,在SpringArmComponent的最後,如下:

OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
OurCamera->AttachTo(OurCameraSpringArm, USpringArmComponent::SocketName);


           ④Finally, we can set our Pawn to be controlled by the default, local player automatically upon being spawned by adding this piece of code:

            最後,我們設置Pawn 爲默認控制,該玩家需添加這段代碼:

//Take control of the default Player
AutoPossessPlayer = EAutoReceiveInput::Player0;

We now have a simple Pawn that will allow us to control our camera comfortably. Next, we'll configure our input in the Unreal Engine editor and create code that reacts to it.

我們現在已經有了一個簡單的Pawn ,讓我們更好的控制我們的攝像機.下一步,我們要配置我們的UE編輯器的輸入,創建代碼,對輸入作出反映.


Work-In-Progress Code     :

      PawnWithCamera.h

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/Pawn.h"
#include "PawnWithCamera.generated.h"

UCLASS()
class HOWTO_PLAYERCAMERA_API APawnWithCamera : public APawn
{
    GENERATED_BODY()

public:
    // Sets default values for this pawn's properties
    APawnWithCamera();

    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;

    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

protected:
    UPROPERTY(EditAnywhere)
    USpringArmComponent* OurCameraSpringArm;
    UCameraComponent* OurCamera;
};

PawnWithCamera.cpp

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#include "HowTo_PlayerCamera.h"
#include "PawnWithCamera.h"

// Sets default values
APawnWithCamera::APawnWithCamera()
{
    // Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    //Create our components
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
    OurCameraSpringArm->AttachTo(RootComponent);
    OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
    OurCameraSpringArm->TargetArmLength = 400.f;
    OurCameraSpringArm->bEnableCameraLag = true;
    OurCameraSpringArm->CameraLagSpeed = 3.0f;
    OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
    OurCamera->AttachTo(OurCameraSpringArm, USpringArmComponent::SocketName);

    //Take control of the default Player
    AutoPossessPlayer = EAutoReceiveInput::Player0;
}

// Called when the game starts or when spawned
void APawnWithCamera::BeginPlay()
{
    Super::BeginPlay();

}

// Called every frame
void APawnWithCamera::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

// Called to bind functionality to input
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);

}

2.Configure Input To Control The Camera 配置輸入來控制攝像機

①We need to decide what our camera controls will do, and then set up inputs accordingly. For this project, let's allow our follow distance to shorten and field of view to zoom in when the right mouse button is held down. Let's also control our viewing angle with the mouse, and our Pawn's movement with the WASD keys. To do this, we'll open the Project Settings from the Edit dropdown menu in the Unreal Engine editor.

我們需要確定我們的相機控制將做什麼,然後建立相應的輸入,在本項目,讓我們遵從按下鼠標右鍵距離縮短和視野放大.我們也通過鼠標來控制攝像機的視角,和Pawn的通過WASD鍵的移動.要做這些,我們需要打開UE編輯器菜單欄的Edit(編輯)->Project Settings (項目設置)

 ②We will need to define one Action Mapping and four Axis Mappings as follows

我們需要定義動作映射和軸映射如下所示

          !!!If you would like to understand more about how input mappings work, you may want to consult the Player Input and Pawns tutorial.

         !!!如果你想知道更多關於輸入映射,參見Player Input and Pawns

Now that we have defined our input, we need to write some code to react to it. We'll head back to Visual Studio to do that next

既然我們已經定義了輸入,我們需要寫一些代碼來實現輸入了,下一步,我們切換到VS

3. Write C++ Code To React To Input

①Our game now has input mappings that we can use, so let's set up some member variables to store the data we receive. During our update, we'll need to know the values of our movement and mouse-looking axes, each of which are two-dimensional vectors. We'll also want to know whether we should be moving toward our zoomed-in or zoomed-out view, and how far between those two states we currently are. To accomplish this, we should add the following code to our class definition

我們遊戲已經有輸入映射了,所以,我們設置一些成員變量來保存接受的數據,在我們的更新中,我們需要知道位移和旋轉角度,他們都是vector,我們也需要知道是否我們應該朝着放大或縮放視圖,和我們這兩個狀態之間的距離有多遠.要完成這些,我們需要在PawnWithCamera.h類定義最後添加如下代碼

//Input variables

FVector2D MovementInput;

FVector2D CameraInput;

float ZoomFactor;

bool bZoomingIn;

       ②We'll need to create functions to track our input, so let's add the following to our class definition inPawnWithCamera.h as well:

         我們需要創建函數來跟蹤我們的輸入,所以,讓我們還要添加如下代碼到我們的PawnWithCamera.h文件:

//Input functions
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void PitchCamera(float AxisValue);
void YawCamera(float AxisValue);
void ZoomIn();
void ZoomOut();

And we can now fill these functions out in PawnWithCamera.cpp with the following code:

PawnWithCamera.cpp 文件中實現這些函數:

//Input functions
void APawnWithCamera::MoveForward(float AxisValue)
{
    MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void APawnWithCamera::MoveRight(float AxisValue)
{
    MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void APawnWithCamera::PitchCamera(float AxisValue)
{
    CameraInput.Y = AxisValue;
}

void APawnWithCamera::YawCamera(float AxisValue)
{
    CameraInput.X = AxisValue;
}

void APawnWithCamera::ZoomIn()
{
    bZoomingIn = true;
}

void APawnWithCamera::ZoomOut()
{
    bZoomingIn = false;
}

         !!!We haven't done anything with ZoomFactor yet. That variable will be updated during ourPawn'sTick function, since its value changes constantly over time based to the state of a button.

         !!!我們還沒有處理ZoomFactor(縮放因子).這個變量將在Pawn的Tick 函數中被更新,因爲它作爲一個按鈕的狀態,值不斷地變化.
           

       ③Now that we have code that will store our input data, we just need to tellUnreal Engine when to call that code. Binding functions to input events forPawn**s is as simple as adding binding code toAPawnWithCamera::SetupPlayerInputComponent**, as follows:

        既然我們能保存輸入的數據了,我們只要告訴UE引擎什麼時候調用這些代碼就行了.綁定輸入函數,如下:

//Hook up events for "ZoomIn"
InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);

//Hook up every-frame handling for our four axes
InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);
InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);

       ④Finally, we can use those values in our Tick function to update our Pawn and Camera each frame. The following code blocks should all be added to APawnWithCamera::Tick inPawnWithCamera.cpp:

       最後,我們能用這些值,在Tick 函數中每一幀更新我們的Pawn和Camera .在PawnWithCamera.cpp的APawnWithCamera::Tick函數添加如下代碼:

//Zoom in if ZoomIn button is down, zoom back out if it's not
{
    if (bZoomingIn)
    {
        ZoomFactor += DeltaTime / 0.5f;         //Zoom in over half a second
    }
    else
    {
        ZoomFactor -= DeltaTime / 0.25f;        //Zoom out over a quarter of a second
    }
    ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
    //Blend our camera's FOV and our SpringArm's length based on ZoomFactor
    OurCamera->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor);
    OurCameraSpringArm->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor);
}

         !!!This code uses several hard-coded values, like the half-second and quarter-second zoom times, the 90-degree zoomed-out and 60-degree zoomed-in field of view values, and the 400 zoomed-out and 300 zoomed-in camera distances. Values like this should generally be exposed to the editor as variables tagged with UPROPERTY(EditAnywhere) so that non-programmers can change them, or so that programmers can change them without recompiling code, or even while playing the game in the editor.

         !!!這段代碼使用一些硬編碼的值,像放大了1/2倍,1/4倍,視角90度變化,60度變化,相機距離400,300.像這些變量值應該用宏UPROPERTY(EditAnywhere)暴露到編輯器中,這樣,非編程人員就可以修改它了,或者說,程序員修改他們也不用修改C++代碼了.

//Rotate our actor's yaw, which will turn our camera because we're attached to it
{
    FRotator NewRotation = GetActorRotation();
    NewRotation.Yaw += CameraInput.X;
    SetActorRotation(NewRotation);
}

//Rotate our camera's pitch, but limit it so we're always looking downward
{
    FRotator NewRotation = OurCameraSpringArm->GetComponentRotation();
    NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
    OurCameraSpringArm->SetWorldRotation(NewRotation);
}

This block of code rotates our Pawn's yaw directly with the mouse's X axis, but only the camera system responds to the pitch changes from the mouse's Y axis. Rotating anyActor orActor subclass actually rotates the root-levelComponent, which indirectly affects everything attached to it.

這塊代碼實現了鼠標x軸控制Pawn的偏航,但是隻有相機系統從鼠標Y.響應俯仰改變,旋轉一些Actor或者Actor的子類實際旋轉了根組件,間接影響附加在上面的一切.


//Handle movement based on our "MoveX" and "MoveY" axes
{
    if (!MovementInput.IsZero())
    {
        //Scale our movement input axis values by 100 units per second
        MovementInput = MovementInput.SafeNormal() * 100.0f;
        FVector NewLocation = GetActorLocation();
        NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
        NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
        SetActorLocation(NewLocation);
    }
}

Using GetActorForwardVector and GetActorRightVector allows us to move relative to the direction the actor is facing. Since the camera faces the same way as the actor, this ensures that our forward key is always forward relative to what the player sees.

使用GetActorForwardVector 和 GetActorRightVector 能使我們朝着面向的方向前後左右移動.由於相機的朝向和actor的方向一樣,這將確保我們的前進鍵總是相對於玩家所看到的方向.

      1.We have finished coding. We can now compile our code and drag an instance of our new class from theContent Browser into the Level Editor window inside theUnreal Engine editor.

                我們已經編寫好了代碼,我們編譯代碼拖一個  實例 到遊戲世界中  

Feel free to add aStatic Mesh or other visual Component, or play without one. You should feel a smooth acceleration and deceleration to your camera's movement as it follows you around the level, but your rotation should feel tight and instantaneous. Try changing some of the properties on the SpringArmComponent to see how they affect the feel of your controls, such as adding "Camera Rotation Lag" or incresing or decreasing "Camera Lag".

我們可以任意添加靜態模型或者可視的組件,或者啥都沒開始拖動.你應該感相機到平穩加速和減速移動.但是你的旋轉是立即的.嘗試着修改SpringArmComponent 裏面的屬性,它們是如何影響你的感覺控制,比如增加"Camera Rotation Lag"或者增加或者減少"Camera Lag".

好了,嘗試着拖動和旋轉這個實例吧(非Play模式下)



Finished Code 完成代碼

PawnWithCamera.h


// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/Pawn.h"
#include "PawnWithCamera.generated.h"

UCLASS()
class HOWTO_PLAYERCAMERA_API APawnWithCamera : public APawn
{
    GENERATED_BODY()

public:
    // Sets default values for this pawn's properties
    APawnWithCamera();

    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;

    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

protected:
    UPROPERTY(EditAnywhere)
    USpringArmComponent* OurCameraSpringArm;
    UCameraComponent* OurCamera;

    //Input variables
    FVector2D MovementInput;
    FVector2D CameraInput;
    float ZoomFactor;
    bool bZoomingIn;

    //Input functions
    void MoveForward(float AxisValue);
    void MoveRight(float AxisValue);
    void PitchCamera(float AxisValue);
    void YawCamera(float AxisValue);
    void ZoomIn();
    void ZoomOut();
};


PawnWithCamera.cpp

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#include "HowTo_PlayerCamera.h"
#include "PawnWithCamera.h"

// Sets default values
APawnWithCamera::APawnWithCamera()
{
    // Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    //Create our components
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
    OurCameraSpringArm->AttachTo(RootComponent);
    OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
    OurCameraSpringArm->TargetArmLength = 400.f;
    OurCameraSpringArm->bEnableCameraLag = true;
    OurCameraSpringArm->CameraLagSpeed = 3.0f;
    OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
    OurCamera->AttachTo(OurCameraSpringArm, USpringArmComponent::SocketName);

    //Take control of the default Player
    AutoPossessPlayer = EAutoReceiveInput::Player0;
}

// Called when the game starts or when spawned
void APawnWithCamera::BeginPlay()
{
    Super::BeginPlay();

}

// Called every frame
void APawnWithCamera::Tick( float DeltaTime )
{
    Super::Tick(DeltaTime);

    //Zoom in if ZoomIn button is down, zoom back out if it's not
    {
        if (bZoomingIn)
        {
            ZoomFactor += DeltaTime / 0.5f;         //Zoom in over half a second
        }
        else
        {
            ZoomFactor -= DeltaTime / 0.25f;        //Zoom out over a quarter of a second
        }
        ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
        //Blend our camera's FOV and our SpringArm's length based on ZoomFactor
        OurCamera->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor);
        OurCameraSpringArm->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor);
    }

    //Rotate our actor's yaw, which will turn our camera because we're attached to it
    {
        FRotator NewRotation = GetActorRotation();
        NewRotation.Yaw += CameraInput.X;
        SetActorRotation(NewRotation);
    }

    //Rotate our camera's pitch, but limit it so we're always looking downward
    {
        FRotator NewRotation = OurCameraSpringArm->GetComponentRotation();
        NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
        OurCameraSpringArm->SetWorldRotation(NewRotation);
    }

    //Handle movement based on our "MoveX" and "MoveY" axes
    {
        if (!MovementInput.IsZero())
        {
            //Scale our movement input axis values by 100 units per second
            MovementInput = MovementInput.SafeNormal() * 100.0f;
            FVector NewLocation = GetActorLocation();
            NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
            NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
            SetActorLocation(NewLocation);
        }
    }
}

// Called to bind functionality to input
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);

    //Hook up events for "ZoomIn"
    InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
    InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);

    //Hook up every-frame handling for our four axes
    InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
    InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
    InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);
    InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
}

//Input functions
void APawnWithCamera::MoveForward(float AxisValue)
{
    MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void APawnWithCamera::MoveRight(float AxisValue)
{
    MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void APawnWithCamera::PitchCamera(float AxisValue)
{
    CameraInput.Y = AxisValue;
}

void APawnWithCamera::YawCamera(float AxisValue)
{
    CameraInput.X = AxisValue;
}

void APawnWithCamera::ZoomIn()
{
    bZoomingIn = true;
}

void APawnWithCamera::ZoomOut()
{
    bZoomingIn = false;
}

    4.On Your Own! 自己動手做!

Using what you have learned, try to do the following:

  • Give the player a "run" key to hold down that will increase the Pawn's movement speed.

  • Experiment with different ways to mix automatic and input-driven camera movement. This is a very deep area of game development with a lot of room to explore!

  • Increase, decrease, or remove the lag from your Spring Component to get a better understanding of how much lag can affect your camera's overall feel.

  • Implement a small amount of periodic motion, possibly slightly randomized or using a Curve asset, to create a "handheld" feel to your camera.

  • Give your Camera some amount of automatic turning, so that the camera gradually gets behind the moving player object and faces the direction the player is moving.

As for the specifics covered in this tutorial:

-------------------------------------------------題目我整理好了發上----------------------------------------------------------------------------

5.Components And Collision 組件和碰撞





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章