2017年11月10日 星期五

如何避免你的程式大爆炸了?

原文連結

在1960/10/24,蘇聯拜科努爾太空發射場發生了一場大爆炸。在準備飛行的期間,火箭意外爆炸了,造成了火災及無數的破壞。超過70個人在這天死亡。USSR的戰略火箭計劃大大的退步。這場大災難被命名為Nedelin,這個計劃死去的執行負責人。

我們重新檢視造成這場大災難的步驟:

1. 工程師在非常緊湊的排程下工作

2. 因為時間不夠,許多安全程序都被省略或者沒正確執行。

3. 許多意外及缺失在發射準備期間被忽略或沒仔細分析過程

4. 在爆炸當天,為了加速發射流程,許多測試被迫不照順序同時執行。

5. 因為火箭啟動的延遲,工程師沒有正常理由就直接在正式環境作業。

6. 當天有人看到發射按鈕沒在原位而將它擺回原始位置。

7. 其它元件也被放在錯誤的地方,造成系統執行發射命令並且點燃了第二階段引擊,使得沒準備好的火箭爆炸了。

對照遊戲開發,我列出了以下狀況


Initialization, update and deinitialization patterns
Bad input data
Too much control exposed in data
Dereferencing null pointer
Big classes
Division
Vector normalization

同時我也提供了如何防範錯誤的做法及範例。

防衛性程式及合約式設計(Design by Contract)

防衛性程式
不該相信輸入的資料,而是預先檢查資料的正確性並給予安全的數值。
下例:即使不正確的參數,仍然給予安全的答案:零。

float GetSquareRoot(float Argument)
{
    if (Argument <= 0.0f)
    {
        return 0.0f;
    }
    return sqrt(Argument);
}

合約式設計:
設定程式與用戶關聯性
程式必須設定前置條件、後置條件和其不變性。

條件可被下列方式呈現:
assert
comment
etc. (a set of test)

下例:使用註解(comment)及assert設定程式的合約
// Argument needs to be equal to or greater than zero.
float GetSquareRoot(float Argument)
{
assert(Argument >= 0.0f );
return sqrt(Argument);
}


這兩項方法都能幫助我們寫出更良好的程式。
無論如何,我相信遊戲程設要能其特別的狀況而被特別對待。

1. 最大化執行速度是遊戲的重點

防衛性程式無法用在程式的每個地方,因為這會拖慢程式速度,且額外的程式會造成其它錯誤。我們的遊戲就遭遇過執行速度的問題。同時太多的防衛性程式使錯誤不容易發現(被防衛性程式擋下)。

2. 程式在變更迅速的環境下被完成。

遊戲程設包含了快速開發、創意驗証。但是因為要求速度會造成未來維護的問題。

3. 程式必須同時能用於產品及測開發階段

當我們在製做新的功能時,編輯器的新工具或改良也在同時進行,我們的第一個使用者是其它團隊成員、設計師、美術人員。他們也在製作遊戲及使用我們的工具。這表示了我們的程式同時被開發團隊及消費者所使用,即使在釋出給玩家之前。用assert強制程式對於工程師是好方法,但是同時會中斷其它成員的工作流程。

另外你要面對的問題可能和你遊戲、團隊、工作模式有關。Rendering的工作就會以速度為優先考量,而AI的工作就會需要更多穩定的程式。

我們將會介紹遊戲開發會遇到的常見幾種狀況。

Initialization, update and deinitialization patterns

讓我們看這個RAII(Resource Acquisition Is Initialization)的實作
class MyClass
{
public:
    MyClass()
    {
        m_Data1 = new DataClass1;
        m_Data2 = new DataClass2;
    }
    ~MyClass()
    {
        delete m_Data1;
        delete m_Data2;
    }

    void Update(float FrameTime)
    {
        m_Data1->Update(FrameTime);
        m_Data2->Update(FrameTime);
    }

private:
    DataClass1* m_Data1;
    DataClass2* m_Data2;
};
我們在constructor創造2個物件,然後在destructor刪除它們。並且包含了一個常見的Update函式,會在每個frame被呼叫一次。

現在讓我們把記憶體管理移出constructor、destructor,並且創造一個函式來控制這些資源。這是為了避免在同一個地方執行太多的操作。通常資源管理是個佔效能的操作,所以我們會希望在遊戲中能方便的控制它們。我們創造2個函式Initialize and Deinitialize來管理記憶體。

這是新的程式碼:


class MyClass
{
public:
    MyClass() { }
    ~MyClass() { }

    void Initialize()
    {
        m_Data1 = new DataClass1;
        m_Data2 = new DataClass2;
    }

    void Deinitialize()
    {
        delete m_Data1;
        delete m_Data2;
    }

    void Update(float FrameTime)
    {
        m_Data1->Update(FrameTime);
        m_Data2->Update(FrameTime);
    }

private:
    DataClass1* m_Data1;
    DataClass2* m_Data2;
};

這程式仍然十分單純,但這有許多的問題。

1 這沒有適當的initialization在constructor中
2 如果Initialize被呼叫了兩次會發生什麼事?這有可能造成memory leak
3 在Deinitialize也有可能會發生相似的問題
4 另外個問題關於Update,若沒有先呼叫Initialize而直接執行Update會發生什麼事情?會因空指標或被初始化物件造成crash.
5 若複製這個物件,有可能造成多次刪除的問題。

我們這裡沒有特別做合約式設計,我們只是考慮這個類別有可能的使用狀況。

為了使此程式穩定、減少出錯。我們需要

1 提供適當的initialization給我們的成員變數
2 加上bool參數m_Initialized 使memory leak、及多次刪除不會發生。
3 正常狀況下,若指標已被釋放,我們應將指標位置清除
4 由destructor呼叫Deinitialize,避免使用者忘記呼叫。
5 定義private copy constructor and copy operator, 防止它們被複製。

更改後如下:

class MyClass
{
public:
    MyClass();
    ~MyClass();

    void Initialize();
    void Deinitialize();

    void Update(float FrameTime);

private:
    MyClass(const MyClass&);
    MyClass& operator=(const MyClass&);

    bool m_Initialized;
    DataClass1* m_Data1;
    DataClass2* m_Data2;
};

MyClass::MyClass()
    : m_Initialized(false)
    , m_Data1(nullptr)
    , m_Data2(nullptr)
{
}

MyClass::~MyClass()
{
    //Prevent a memory leak if the client 

    //does not call Deinitialize.
    Deinitialize();
}

void MyClass::Initialize()
{
    if (m_Initialized)
        return;

    m_Data1 = new DataClass1;
    m_Data2 = new DataClass2;

    m_Initialized = true;
}

void MyClass::Deinitialize()
{
    if (!m_Initialized)
        return;

    delete m_Data1;
    m_Data1 = nullptr;

    delete m_Data2;
    m_Data2 = nullptr;

    m_Initialized = false;
}

void MyClass::Update(float FrameTime)
{
    if (!m_Initialized)
        return;

    // Defensive approach to check the pointers separately.
    if (!m_Data1 || !m_Data2)
        return;

    m_Data1->Update(FrameTime);
    m_Data2->Update(FrameTime);
}

Bad input data
(壞掉的輸入資料)

壞掉的輸入資料從資訊產業誕生的那一天就有了。檔案遺失或例外參數都會造成遊戲或編輯器當掉。

通常工程師會特別注意這問題。我們的程式需要為了參數遺失,特別寫預設的參數,像是預設的貼圖或可視的物件來提醒輸入錯誤,給予錯誤訊息及記錄也是有效的方法。沒有程式應該直接掛點。

在多數的遊戲引擊,我們可以很輕易地給予編輯器參數。讓我們可以利用這參數在一行指令就生成數個物件。

int NumberOfProjectilesToSpawn;

通常我們只會給他0,1,5這種數量。但有時使用者會犯錯或給與一個超大的數值像是100萬、或負數?那時候我們的程式碼還可以正常運作嗎?

好的方法是去考量現況決定參數的預設值及範圍。另外一種方案,如果我們想要不被限制的超大參數,我們提醒使用者輸入的參數可能會造成問題。

在防衛性程式,我們可以很清楚的區分有危險的參數。我們將所有input視為不可信的,但有些被驗證的內部資料是可以相信的。例如,我們認定所有外部檔案、編輯器輸入參數、玩家輸入參數、網路上參數都是有不可信的。驗證及修正函式需要將這些參數轉成可信的參數。

void SetNumberOfProjectilesToSpawn(int NumberOfProjectilesToSpawn)
{
    m_NumberOfProjectilesToSpawn = NumberOfProjectilesToSpawn;
    ValidateAndCorrectParameters();
}

void ValidateAndCorrectParameters()
{
    m_NumberOfProjectilesToSpawn = clamp(m_NumberOfProjectilesToSpawn, 0, 8);
}

在遊戲開發中,越早驗證是越好。例如:在編輯器輸入參數時就驗證,在存成檔案之前,或是在檔案一讀進來。越早確認使其更容易減少執行遊戲不必要的測試及效能衝擊。

Too much control exposed in data
(太多對資料操作的方式)

由壞掉的輸入資料所造成的問題再更進一步思考,我們所有的系統都是資料導向的。這些系統能被設定或在沒有工程師的狀況下被操作,完全透過所輸入的資料。

通常會將所有操作及遊戲特色給每個組員,尤其是非工程師。但是工程師必須問問自己,我們真的可以提供所有組合參數嗎?或者反而造成更多例外狀況?

創造一個真正資料導向的系統是非常困難及花時間的。

當我們提供任一個參數,這都會成為整個巨大資料導向系統的一部份。很自然的,我們會加入一個介於程式與資料之間中間層。

讓我們看這用來控制敵人的簡單結構

struct EnemyBehavior
{
    float m_WalkSpeed;
    float m_TurningRate;
    bool m_JumpAllowed;
    float m_JumpSpeed;
    // …
};

這個程式提供m_WalkSpeed 的小參數,也同時提供m_TurningRate 大參數。當m_WalkSpeed 很大時跳躍的功能仍然可以正常運作嗎?

一個較安全的方案是準備一組預先設定好的參數組,而這些參數都是可正常執行的。使用者只允許使用這組先定好的參數。

enum EnemyBehaviorPreset
{
    EnemyBehaviorPreset_TightCorridors,
    EnemyBehaviorPreset_IndoorHalls,
    EnemyBehaviorPreset_OutdoorOpenSpace
};

我們只提供一個參數讓使用都可以在編輯器使用。

EnemyBehaviorPreset m_EnemyBehavior;

這個方法雖然限制使用者的自由,但是對工程師有更佳的穩定及維護性。

Dereferencing null pointer
釋放空指標

Dereferencing null pointer是造成遊戲或編輯器當掉的一項常見因素。

在Unreal Engine 2 and 3,其中一項程式語言UnrealScript的設計目標就是防止這類問題。並且創造不需指標的開發環境。存取物件references在UnrealScript一直是安全的,即使它們沒有指向任何物件。

因為執行速度是很重要的,所以我們不能在所有釋放指標的地方做檢查。無論如何每個部門處理指標的方針也許不一樣。繪圖工程師會犧牲防衛機制去換取速度,但是AI或gameplay則會希望更多的防衛機制,尤於gameplay時常更動。所以如何處理指標會按其環境而定。

-Avoiding pointers(避免使用指標)

第一、我們可以避免使用指標當函式的參數且用C++參照(references)取代,下例即是說明如何取代指標

void MyFunction(ControlData* InputData)
{
    if (InputData)
    {
        int parameter1 = InputData->GetParameter1();
        // …
    }
}

改成用reference的版本

void MyFunction(ControlData& InputData)
{
    int parameter1 = InputData.GetParameter1();
    // …
}

這樣使用者在用這個函式時就會提供適合的參數,並確認及釋放指標。

-Public and private methods(公開及私有函式)

實際上,有項不好的慣例是在公開的函式傳入的指標當作參數,

若這函式是屬於公開介面的話,我建議要檢查這函式是否為空。

void MyClass::AnalyzeInputData(Data* Input)
{
    if (!Input)
        return;
    // …
}

若函式是屬於私有實作的一部份,我會傾向將先前的程式移除。在這例子,函式有註解標明、或是函式會發出警告訊息去確認指標狀況。

class MyClass
{
public:
    // Method always tests if the pointer argument is null.
    void AnalyzeInputData(Data* Input);

private:
    void UpdateObject_NoTestForZeroPointer(ObjectClass* Object);

    // Method does not check validity of pointer arguments:
    void UpdateObject(ObjectClassA* Object1, ObjectClassB* Object2);
};

另外在私有函式UpdateObject及UpdateObject_NoTestForZeroPointer的開頭,我們可以利用assert驗證指標

// Method does not check validity of pointer arguments:
void MyClass::UpdateObject(ObjectClassA* Object1, ObjectClassB* Object2)
{
    assert(Object1);
    assert(Object2);
    // …
}

無論如何這不是最實際的做法,若我們的assert中斷成員在使用編輯器。要改進這個方法,我們可以創造一個防禦性軟驗證。

-“Soft” assertions

軟驗證應該允許程式繼續執行,但仍會回報問題給開發者

void MyClass::UpdateObject_NoTestForZeroPointer(ObjectClass* Object)
{
#ifdef CONTRACT_SAFE_CHECK
    reportIfNull(Object); // Soft assert
    if (!Object)
        return;
#endif
    // …
}

這方法通知程式人員不正常的狀況(將訊息送到資料庫)且不會中斷執行。利用#ifdef設定是否要執行這項檢查。開發設定應效能分析(profiling)及正式環境可執行。

編譯前的參數也要考慮到是否在編輯器執行或是遊戲內會造成其它問題。

Big classes

有些class成長速度太快了。所以有些原始檔會變得比其它檔案還大。若你去檢查你正在執行的專案,你會發現有些檔案很明顯地較其它檔案大上不少。這些檔案通常是負責角色、玩家或是遊戲管理者(manager)。這並不令人驚訝。當我們增加新的特色進遊戲時,即使是最小的步驟,我們也會將它加入現有的class底下。這種增加速度是會造成問題的。這使得我們很難去維護preconditions, postconditions and invariants。維護大型的類別是很困難的,且他們也會造成更多問題。從Initialization, update and deinitialization patterns要發現問題會比較簡單。通常他們包含了許多沒有分類好的子元素。這有許多可能的方法可以解決這個問題。我在這展示其中一個方法。

若我需要在現有的class中加入一項新的元素,我開始評估是否該為其建立nested class。下例在ObjectBehavior 內新增成員參數及函數去為了完成Special Action 1。

class ObjectBehavior
{
public:
    // …
private:
    // …
    // Begin Special Action 1.
    bool m_ShouldStartSpecialAction1;
    float m_TimeLeftToStartSpecialAction1;
    float m_SpecialAction1Parameter;
    void InitializeSpecialAction1();
    void DeinitializeSpecialAction1();
    void UpdateSpecialAction1();
    // End Special Action 1.
    // …
};

以nested class方式實作:
以子類別
class ObjectBehavior
{
public:
    // …
private:
    // …
    class SpecialAction1
    {
public:
        void Initialize();
        void Deinitialize();
        void Update(ObjectBehavior* ParentObject);
    private:
        bool m_ShouldStart;
        float m_TimeLeftToStart;
        float m_Parameter;
    };

    SpecialAction1 m_SpecialAction1;
    // …
};

哪一個方法比較好?我們適度的包裝了新的參數在獨立的私有類別中。只有SpecialAction1 nested class被存取到。這使得類別在未來可以更容易被區分出來。

除法(Division

電玩遊戲用了大量的浮點數運算。例如,我們經常使用除法在我們的程式內。這是非常基本的數學運算,但這仍然要注意不要讓除數為0。若我們除數為0時,會造成浮點數的例外狀況,或者浮點數的例外被擋掉了,算出來的結果會是無限(INF)或非數字(NaN)。

請也考慮到下面的狀況。當兩個數字都是合法的浮點數,但是結果會是INF,因為他們超過32-bit浮點數可表現範圍。

float f1 = 100000000000000000000.0f; // 1.0e20f
float f2 = 0.0000000000000000001f; // 1.0e-19f
float f3 = f1 / f2;

f3即為INF。另一個例子:

float f4 = 0.0f;
float f5 = 0.0f;
float f6 = f4 / f5;

參數f6是NaN

我們可以繼續執行程式,因為INF是個合法且可計算的特別記號。無論如何,在多數狀況下,我們不會想去處理這種狀況。許多函式庫會因這些例外狀況中斷。

Unreal Engine 4 提供了時別的函式去偵測浮點數太接近零。

bool FMath::IsNearlyZero(float Value, float ErrorTolerance = SMALL_NUMBER);

在 C# Unity Engine可以被寫成這樣:

bool IsNearlyZero(float Value)
{
    return Mathf.Abs( Value ) <= 0.00000001f;
}

我們可以在做除法之前先確認我們的除數。

float denominator = b – a + c;
if (!IsNearlyZero(denominator))
{
    result = d / denominator;
}

要確認特別的浮點數,我們擁有以下函式:

float.IsInfinity(float) and float.IsNaN(float) in Unity Engine
FMath::IsFinite(float) in Unreal Engine 4

同時我們經常忘記某些特別狀況會用到除法。請考慮以下的update函數。

void Object::UpdateLogic(float FrameTime)
{
    Vector3 Velocity = (GetCurrentPosition() – m_PreviousPosition) / FrameTime;
    // …
}

我們可以很簡單的假設FrameTime總是大於0。當然程式會在FrameTime為零時出錯。這是有機會發生的,例如當遊戲在製作時我們相要將時間暫停,但是update仍然被呼叫。在編輯器,我們通常不會更新world logic,但在某些特別的狀況,我們會為了模擬而更某些物件的邏輯。這簡單的程式會因為這些特例而掛掉。

Vector normalization

在Vector normalization會出現某些例子上。通常是計算3d數學時。normalization是為了計算出單位向量的方法,會將其向量除以自身長度。這些函式都非常單純,我們再次回想起了除法的例外。當向量為零時或非常接近零時會出錯。

在Unreal Engine 4,我們有以下兩個函式:

FVector::GetUnsafeNormal() – 這是不會做額外檢查的normalization,但比較快速。

FVector::GetSafeNormal() – 這是會檢查除數是否為零,若長度接近零時,結果回傳零。

有趣的是它用很直接的寫在函式名稱上提醒使用者。

Unity Engine在C#則是只有提供安全的normalization

Vector3.normalized

Vector3.Normalize()

所以它會回傳單位向量或是零向量。

“Soft” methods

我同時也會提供些通用的方式來提升程式品質。

測試你自己的程式碼:
自動化測試、
視覺化除錯及文字記錄、
採用有意義的命名、
程式檢閱(code review)、
靜態程設分析工具、
程式文件

總結:

在這文章中,我展示了遊戲程式設計上常見的錯誤及一般問題的來源。我們知道追求安全及穩定的工程方法並非新的挑戰。這也不是簡單的工作,尤其在特殊的狀況下,像是遊戲開發。主要困難來自於去知道、預測及決定什麼地方該用防禦性程式及該用多少的安全措施才足夠。

將高安全性軟體工程的做法完全放到遊戲開發是不明智的。例如:太空計劃。無論如何,這可以用來了解人們在這些產業是如何完成他們的目標。

NASA太空中心主管及Saturn V rocket首席工程師,特別喜歡實作大型及安全的軟體工程。這些通常會在開始時花不少時間,但最終帶領美國完成了阿波羅11的成功。第一個上太空的人也安全的回來了。Saturn V 也是唯一帶著人類離開地球的火箭。


2017年9月25日 星期一

Failbetter Games首席編劇的五點建議

原文連結

在經過了幾年的鬥爭及無數個敵手的死亡,去年我成為了Failbetter Games的首席編劇。我管理內部的劇情小組及外包工作,包含有Sunless Sea, Fallen London, Dragon Age: the Last Court及其它專案。

我仍然在尋找正確的道路!這裡有五項我過去學到的教訓。

1 優先提升團隊能力,即使這需要犧牲劇情的品質。

"這份專案是Fallen London,別把他搞砸了"

很明顯的我當時十分緊張。我必須完成這個專案,我是個超級的完美主義者。如果這有曾經做錯的事,就是要團隊照著範例做事。

我深信提供案例可以讓團隊快速學習,但這包裝好的學習方式,使我放棄了團隊自我成長的機會,他們可以自己克服困難並發展出他們自身的解決方式。而我應該學會退後一步並點出幾個重複的問題。我的建議必須是清楚、泛用、可達成的。例如:"將字數減半"、"確定玩家知道自己的每個步驟"。

團隊提出的解決方案通常比我的還好。這花了我一段時間了解,我最重要的工作是去幫忙團隊去做到最好。有時候這代表著你要咬緊牙關讓團隊去做你不喜歡的事。而且總是會不斷發生。

2 防止你的團隊的不斷的提出新點子

我在這件事上有著慘痛的經驗。我們是創意產業,每個人都會貢獻自己的意見。我們有邏輯及也會批評(必要時)。如果你的團隊提出一大堆不實用的點子,當你是個管理者時,你應該要坐下和他們好好談談,幫團隊踩剎車。

問些嚴肅的問題,尤其是他們並沒有辦法確實回答(例如:"這有商業的案例嗎?"、"這件事是如何做錯的?"、"這件事真的實踐會花費多少時間")。如果這些點子不能通過你的檢驗,那自然也無法通過別人的檢驗。

最差的狀況是一個無法執行的點子沒被阻止。它進入了開發流程並且浪費了許多資源,且最終還是必須結束它。沒有任何一個人會因此開心。

3 不得不犧牲好點子

有時候你必須對些好點子說NO。因為現在不是適當的時間、適當的專案、適當的團員。但是不要就這麼浪費它們。將他們記下來。規劃時間去檢視它們。過了三個月後,它們也許會看起來不一樣。

4 你的知識是珍貴的,保護並且栽種它

了解小說世界的基礎。他的定義是什麼(哥德!),不是什麼(蒸氣龐克!)。闡述你的故事。不斷檢視更新對它的認知。使用書籍、影集、電視、音樂為它寫評論:"這感覺像是Melville,而不是Roddenberry"。評論自己的每次更新,不只是內部的,對外包工作者也是。

外包工作者並不會被工作環境每天影響。為了補足這部份,我們會讓團隊及外包工作者加入同一對話群組。用這方法,他們可以提出問題並參與部份團隊的討論。

5 培養自主權

Fallen London有超過150萬字。Sunless Sea則有30萬字。一個人的腦袋很難去管理這些。我們可以將這些做成摘要文件,或讓我們的文件系統是可以搜尋的。但是前者很難完成、而後者很花時間。

取而代之,我們開始讓團員擁有它們自己的劇情。像是Cash創作出了角色W,且我希望在故事中使用W,我會請Cash來幫助我了解問題。如果對開發角色有不同意見的時候,Cash會被負責做最終決定,這很有效率,而且對創意有正面的回饋。

2017年9月16日 星期六

深入遊戲設計:創造適合Reigns的故事

原文連結


Who: Nerial的遊戲設計師François Alliot


我是一位獨立遊戲設計師及遊戲開發者。我開發及設計Devolver Digital發行的冒險遊戲Reigns。我從三年前開始做遊戲,這是我的第二項職業。


What:撰寫包含機率的故事


與美術Mieko第一件決定的事就是遊戲的核心機制:利用滑過去的動作(有點像是Tinder)使國王選擇2個王國的選項,透過一組卡片來表示你的不同顧問。

每個選項都會衝擊到你的王國,你的目標就是讓王座能維持越久越好。事情很快變得越來越糟糕,而你會在各式狀況下悲慘的死去。

一開始、Reigns很像是債務管理的模擬遊戲:你必須去平衡四項權力(宗教、人民、軍隊、金錢)且應付顧問提出的各項選擇。

初始遊戲機制在情感及實際都運作的很好。這是一個隱含驚喜的玩具(一個很聰明的人某天告訴我的)。我們評估選項對四項權力的影響。我們給予這個玩具深層的意義。

前十分鐘我們感到很有趣。相信我這是個很好的開始。我們只需要再吸引玩家2個小時。

我們發現了玩家很快就把50張設計的卡片給玩完了。在不同的事件間又產生了不同的意義,我們自己從未想過的連結。像是飢荒或婚約。

我們決定建立在這結果之上,更加充實玩家在這前十分鐘所創造的故事。



Why: 為什麼?


為了達成這目標,我們將隨機抽出的卡片加入機率系統。這很難用簡單的方法來解釋。

想像一個包包擁有適合你內容的大小。一般來說,這包包含有所有遊戲卡片。當我選出一張卡片時,我開始將不適合這王國現況的卡片由包包中移除。

這有很多的組合,例如:如果這區塊的卡片都和皇后有關,但是你還沒結婚,就會被移除。像是卡片只會在宗教強大的時候觸發,但你的宗教很虛弱,也該被移除。同時也移掉玩家最近玩過的卡片。

這創造出整組玩家每次都玩不膩卡片。最後讓卡片都是佔有不同空間。卡片越大張會佔住包包越多空間。

最後你會擁有一個混合卡片的背包,大的卡片佔住大部份空間及其它小卡片。我就可以從包包內挑一張卡片來給玩家看。

卡片佔空間造成有趣的現像。大卡片被選到的機率大於小卡片。但是若大卡片在前幾步已經被抽掉了,那剩下的卡片機率就會大大增加。

這就是包包的擴張、縮小尺吋能力為什麼這麼重要。若你與鄰國開始了戰爭,那與些戰爭相關的10張卡會佔比較大的機率。且會把包包大部份的空間佔去,大於其它不相關連的卡片,例如小丑卡。這最大卡你挑到戰爭相關的卡片,而縮小挑到小丑的機率。

一旦戰爭結束,戰爭卡將被移出包包,系統有更大的機率去挑到小丑卡,因為它的機率上升了。



有些事件、像是地下城、決鬥會把系統縮限在一小塊區域。這時包包會非常小,只包含一點點的卡片。這小區域也可能只會有一張卡片,用來創造線性的故事(像是遇到惡魔)。

這機率系統的多樣性是創造遊戲的關鍵。我認為這樣的工具或規則來使遊戲變得奇幻及不可預期,甚至比明確定義故事內容的封閉系統還要完美。

我不介意Reigns是不完美的,這是這遊戲的不可缺少的一部份,這就是遊戲的特色。我們總是小心的創造每個事件能圍繞遊戲核心(有趣及遊戲特性),但我們不特別限制讓玩家特別磇到驚喜劇情(或者干擾玩家)。

創作Reigns真的是件類似探索的工作。你可以把事件照你所想的一樣緊緊包在一塊。我從單張的卡片開始,接著嘗試短時間的短篇故事線,然後持續增加,像是新角色及新的事件永久加入牌組,再來是惡魔的故事線及一堆圍繞著核心遊戲的卡片,帶領玩家進入未知的體驗。

例如(背叛者):有在森林中盲目奔跑、挑選奇幻香菇、在瘟疫中破解大臣的問題、處理沒有人性的奴隸問題、如何在愛情中表現適當、進入鴿子世界、和鴿子約會...



結論:


透過這個系統,我們可以創造不確定的故事,在非常廣泛的劇情卡、特別事件的分支劇情、碰到連製作團隊都沒遇過的特別組合。

Reigns遵從著Failbetter Games(製作SUNLESS SEA的團隊)高品質故事的製作方針。我們的品質來自於每次在有限的包包內放置的卡片組合。

最後,Reigns的核心體驗混合了每次所選的卡片,由特定方式隨機抽出的組合。這產生出非常有趣的組合。一但玩家發現有些牌是由前些選項所造成的,會使得每張卡都具有意義,因為玩家很難去辬別出隨機卡片會怎麼出現。

即使一小部份的卡片讓你覺得曾經看過,但整個遊戲會變得奇幻且難以預測,促使玩家去創造他們的故事。誰能說發現奇幻香菇不是僱用女巫造成的影響?又或者是瘟疫的一部份?

我也不能如此斷言。

2017年8月31日 星期四

[翻譯]Darkwood的開發故事

原始連結:http://imgur.com/a/xVhDz
如果你喜歡這些內容,可以去原始連結給製作者一些鼓勵。

我們都很害怕玩恐怖遊戲,所以我們離職去做一款沒有jump scares的恐怖遊戲。這是我們的堅持,我們的故事。

我們是Acid Wizard Studio。3位從波蘭來做遊戲的大學生(及一條狗)。經過4年的開發,我們剛剛將Darkwood完成了。一款由上往下視角且沒有jump scares的恐怖生存遊戲(但是仍然能嚇你一大跳)。

一開始我想先說這不是一趟輕鬆的旅程。事實上,我們每一個人在開發過程中都經歷過崩潰的低潮。

在做Darkwood之前,我們並非恐怖遊戲的玩家。在開發遊戲的過程,我們計劃逐漸克服自身對於恐怖遊戲及電影的恐懼。即使如此,Alien: Isolation對我們來說還是太可怕了。

在開始做Darkwood之前,我們並不太瞭解遊戲開發。我們在動畫及視覺設計上有經驗,但是在製作、程式、遊戲設計上沒有太多的涉略。

最原始的想法是在一個月內做一款塔防遊戲。
在過了無數個沒有睡眠及惡夢的夜晚之後,啟發了這個專案未來的雛型,我們在2013年做出了這個遊戲的雛型。我們利用它做出了遊戲展示影片,然後玩家的反應大大的超出了我們的遇料。

這個時候我們覺得這是個遊戲開發的大好機會,所以我們後半年開始在Indiegogo上募資,幫助我們完成遊戲。這次募資比我們原先所想的大大幫助了我們。

在募資本來狀況並不好,玩家覺得我們無法做好它。但是在最後一週,我們釋出了第2個遊戲的展示影片。這讓我們得到很多的助力,且讓募資趨向成功。我們訂下遊戲的完成日期為西元2014年。還真是天真的想法。

不幸的,我們在開發上的缺乏經驗使得日期大大地延遲。我們擴大了Darkwood的規模,及在遊戲核心上做出了些重大的改變,這花掉了我們很多時間。

財務上的問題迫使我們需要在Steam Early Access推出。玩家給予遊戲很好的評價,且銷售狀況也大大出乎我們意料。這鼓舞了我們,我們打算再次的將遊戲的規模擴大。

在這之後我們持續投入心力來儘快完成這遊戲。我們原定計劃沒有一項是準時完成的。由於過慢的開發速度,玩家的負評開始湧進來。我們也因此被影響使得開發速度更慢,甚至興起了放棄這遊戲的念頭。

開發變得非常的緩慢且沮喪。我們選了一條遠超過我們所能承擔的道路,但是我們知道我們擁有和別人不同的東西。我們必須完成我們理想中的遊戲。

現在是2017年,我們終於辦到了。遊戲已經發佈了1個禮拜。遊戲的評價都非常正面,我們被玩家的反應嚇到了。

我們沒有很多的時間來慶祝,還必須花很多時間來修正錯誤,解決玩家遇到的各式問題。我們其中一位成員(Gustaw)最近才剛滿30歲。他花了一整個晚上嘗試決解玩家遇到的存檔問題。

我們被湧入的信件所淹沒了。它們大大的超多我們所能回覆的數量。不幸的是有一大部份是圾垃信件。有些人稱自己是實況主或部落格來索取遊戲序號。這些序號有些流入了二手交易平台。事實上,我們十分厭惡這件事。這使我們無法從這些事情中脫身,或者將序號真的送到沒有錢購買遊戲的人。

Steam允許玩家在遊玩2小時內退貨,而且開發者可以看到這些人退貨的理由。我們看到一位玩家因為不想被家長看到月底的消費帳單而退貨。這讓我們感到十分不開心。

我們決定做件事來改變這狀況。如果你沒有錢可以買這款遊戲,我們放出了安全的種子在Pirate Bay,完全不綁定平台(DRM-free)。裡面沒有添加任何的惡意修改。我們只有一個要求,如果你喜歡這個遊戲,並且希望我們未來能持續做遊戲,請考慮未來在Steam、GOG、Humble Store買下它,即便是特價期間。但請不要在序號交易網站購買。若你在序號交易網站購買,你只會助長了非法交易的人們,他會逐漸侵蝕這整個產業。

感謝大家看完我們的故事。

2017年6月9日 星期五

[翻譯]Outlast是如何誕生的(下)

原文來源
上篇

遊戲開始了


由於這款遊戲要能被一隻小團隊所完成,我們需要讓這團隊具備所有能力。我們第一個雇用的人是Rejean Charpentier,一個了解Unreal並且有能力完成各式gameplay的軟體工程師。他和我們一同在解雇前在EA所停掉的專案工作。我們接著雇用一個有經驗的動畫師及美術人員Alexandre Sabourin,他有技術也有美術的天份。接著是另一位的gameplay的軟體工程師,Patrick Lalonde,,他擅長遊戲AI。

我們開始尋找角色建模師(character modeler)。我們需要外包人手,但我們在Montreal無法找到一個自由接案者,我們也不想找一個遠端工作的人。我們知道我們沒有時間去管理遠端工作。最後我們找到了Marc-Antoine Senécal,他只願意以全職工作的身份加入我們。我們當時不確定是否有足夠的工作給全職的角色建模師,後來證明了雇用他是正確的決定。

因為之前18個月在尋找資金的時間,也使得我們有很多時間去思考遊戲並且規劃流程。到了此時,團隊成員加入後,我們很清楚我們打算做什麼。即使還是有許多的事情需要去處理,但主要工作一直是不變的。所以我們與Game On Audio的作曲家Samuel Laflamme、及動畫師立刻開始製作demo。

我們的demo並不如想像中進展順利。雖然我們目標已經確定了,但在製作細節上,仍然花了許多時間。因為demo是遊戲製作的起點,所有玩家第一次見到我們遊戲的時機,它應該要是很完美的。雖然我們很希望能在10月底完成,但更重要的是讓整體工作流程能順利進行。

團隊內部的建議使得我們做了不少更動。做恐怖遊戲困難的部份在於恐懼是種讓人有意料之外的感覺,我們可以知道如何製作恐怖的事件,但很難評估它的效果如何。所以藉由外部人員提供反饋並加以調整是很重要的。

在萬聖節到來時,我們公開了遊戲demo。它受到很好的迴響,大幅地超過了我們的預期,讓我們真的被注意到。看著youtube的觀看次數持續上升,讓我們受到很大的鼓舞。

我們持續地製作遊戲demo,音樂整合是一個印像深刻的事件。在某個星期五的下午,我們一起把遊戲跑過一遍,大部份的開發人員還沒聽過音樂及音效,當NPC抓到了玩家並響起追逐音樂時,這就是我們所要的緊張感。

並非每一件事都是完美的,我們的動畫師就進展的不太順利。我們需要一位更加有經驗及自主的人。

我們雇用了新的動畫師Stefan Petryna,他曾經參與Far Cry 3製作,也有第一人稱遊戲的經驗。我們需要他立刻加入團隊並讓工作順利進行。

我們完成了第一次的遊玩測試,雖然不算是大災難,但也是個新遊戲的第一次上場。玩家可以看出遊戲的潛力,但我們要完成目標仍有許多工作要完成。這是個艱難的時刻,因為我們感到時間的壓力,以及知道要達到完整的產品但卻沒有明確的目標是個大災難。我們沒有延遲計劃的空間。我們曾經想過引入其它投資使我們有更多時間,但最後我們還是決定以現有的資源完成這遊戲。

我們從遊玩測試得知遊戲步調是其中一個大問題。玩家相當的融入遊戲的世界中,但是感到恐懼的時間太晚了。玩家覺得自己不是主角,而是個旁觀者。我們決定將第一章的一部份拿掉,將它放到第三章,在玩家次次到這個區域時。我們在這第一章加入一些恐怖的片段,使它變得完全不一樣。

另一個問題在於控制攝影機及開門的機制,這看來不夠直覺,需要讓它更加流暢。

視覺及音樂呈現出的整體氣氛受到玩家不少好評。玩家可以感受到我們營造的張力,並有動力去繼續探索遊戲。

我們花了大約2個月去解決這些問題,這段時間,我們出現了資源不足的問題。我們會再需要一個環境美術及在演出過程的動畫師。幸運的是,我們用不著多少時間就找到了正確的人手。Patrice Côté是曾經參與Splinter Cell: Conviction及Thief並且熟悉Unreal 3的美術。Jamie Helman是擁有15年經驗的動畫師,參與過Army of Two、Dead Space 2。他們加入的第一天就馬上開工了。

在2013年2月,我們已經準備好給記者的遊戲展示報導。我們看著他遊玩,並且開心的看著他被嚇到、大叫、咒罵。他寫了一篇非常正面的報導。雖然並非每件事都很完美,但我們朝著目標努力著。我們已經準備好完成遊戲剩下來的部份。

我們的下一個目標是PAX East。我們讓部份人手專注於這次展示,剩下的人繼續完成遊戲。我們試玩有40分鐘長度,為了這次展示需要將它濃縮到15分鐘。經過一番修改之後我們完成了。我們覺得這版本效果不錯,但不知道在PAX時是否能嚇到玩家。

為了增加遊戲氣氛的衝擊,我們把攤位佈置成可以隔離玩家,並儘量讓他們待在黑暗中。我們提供了2個由布簾隔開的座位。這表示玩家經過時沒辦法看到遊戲展示。我們也去找了所能找到最高級的抗噪耳機。我們感到十分焦慮,不知道到時會發生什麼狀況。

來我們的攤位試玩的玩家慢慢地增加了,尖叫聲開始出現在現場。玩家的試玩及在排隊狀況讓我們覺得很滿意。有兩次玩家因為嚇的跳起來撞到了攤位的牆壁。到了第三天,玩家願意花2小時排隊來試玩,我們非常高興。

最後衝刺


當我們回到Montreal時,整個團隊都很興奮。但我們還有很多工作必須在期限內完成。我們訂下了一個不允許出錯的緊湊計劃。我們剩下的時間只剩下各一次的內部與外部測試,但是並沒有時間去規劃測試回應後的修正。

我們沒有其它選擇了,這個遊戲必須在2013年秋天推出。

幸運的是我們的團隊已經完全進入狀況。所有人明白這是一個大挑戰,我們只能夠成功。

在過去我所參與的案子中,有件事是不變的。在開發過程,團隊會覺得自己是在處理一堆垃圾。這些狀況在我所參與的好遊戲、壞遊戲所感覺是一樣的。在製造的核心中,團隊因為將遊戲看的太仔細。沒有遊戲是完美的,所有遊戲都有Bug存在。這需要決心及有根據的信仰來克服這困難,在當時狀況下做出最好的表現。最重要的是,你要傳達給玩家創作的初衷及承諾。像是Outlast,它的初衷就是要把玩家嚇的半死。

在2013年7月初,我們做了外部測試。我們聽到有個玩家在精神病院外面遊蕩了40分鐘,因為她覺得壓力大到不敢進去。我們雖然有點罪惡但感到十分滿意。我們嘗試創造感情上的雲霄飛車而且看起來成功了。

大部份的玩家反應都很好,但是有些部份還需要再加強。遊戲的結尾是其中之一。

不幸的是,大多數遊戲,你在做結尾的時候通常時間也不怎麼多了。你雖然想要把它好好完成,但是不可必免的,你必須和現實妥協。在製作開始時,我們希望在黑暗恐懼的氣氛下結束遊戲,這是傳統恐怖電影的方式。但我們第一個版本的方向並不正確。

我們的動畫師們提出了一些有趣的點子,即使我們只有幾個星期的時間能夠完成。因為這要加入更多過場事件且調整目前的時間點,這表示聲音、音效也要跟著調整。這使整個團隊造成很大的壓力。

我們知道Outlast的結局有些玩家不太喜歡。我仍然不太確定是執行的問題還是氣氛不合。但是我們過幾個月所做的DLC的結局,比原來的結局更加合適。

在2013的夏天我們沒有看到幾次的太陽。這是個壓力極大的關鍵時刻,但這也是一個好遊戲及優秀遊戲的差別。製作上的最後衝刺會使這遊戲變的更加驚人。

我們的孩子誕生了

Outlast離完美還很遙遠,有些系列作的第一款作品常出現的問題,像是重複性過高、太少變化、急促的結局、關卡太短。我們從多數玩家得到的反饋,很榮興地,它有將其初衷傳達給玩家。

在2013 9月4日遊戲PC版發佈了,那天晚上我們很晚才入睡。即使我們做了所有的測試,它仍然存在兩個重大的BUG需要儘快修正。其中一項是可以在出貨前,最低需求配備的電腦上被測出來的,但我們太早停止在那台電腦的測試。另一個BUG是在某些電腦上DLL會造成衝突,但是剛好測試用電腦都沒有發生。有位玩家花了一整晚幫助我們的軟體工程師找出問題。我們將更新寄給他測試。我們非常感謝他並且將他加入遊戲製作的感謝名單。

玩家評價不斷的冒出來,有些正評,也有些沒有那麼好。但我們對整體評價感到滿意,尤其是在看到恐怖遊戲專門網站的評價時。

在發行後,我們計畫做DLC,此時工程師正在做PS4及Xbox One的版本。所以我們費了一番功夫才開始製作DLC。

我們快速的明白我們將會需要雇用兩位重要的幫手,Mathieu Gauthier,繪圖工程師(rendering programmer),他將幫助我們接下來的跨平台及強化視覺工作。開發Outlast也讓我們知道將會花費很多時間在臨時的音效外包上,所以我們也雇了一位音效專家Francis Brus。

不幸的是,這表示我們必須讓我們第二位遊戲工程師離開。我們還不確定未來的營收如何,所以我們沒有足夠的資源同時雇用三位工程師。

我們花了一些時間分析評論及玩家的反饋來決定DLC要做什麼改進,不幸的是工程師此時正忙於跨平台的工作。我們決定去強化我們的寫作工作。

在2013年11月,我們計畫花六個月去製作DLC。因為我們想要強化敘事效果,這代表了兩件事,更多的過場事件及對話。同時也增加了動畫師及音效人員的工作。

接著我們發出了PS4的版本。我們和SONY達成了協議,使PS+的玩家能免費下載這款遊戲。這件事在財務上很難說是正確的。但有件事是肯定的,我們開發下個系列作品時,我們會擁有更多聽過我們作品的玩家。我們也確實地達到了目標,在第一個月PS+就有1,800,000人次的下載。

在2014年5月6日DLC:Whistleblower在PC及PS4上推出了,過了2個月後,主程式及DLC也一起在Xbox One推出了。

本來並沒有打算在次世代主機上推出。因為那個時間,我們在專注於PC版本,但是SONY希望我們能將Outlast出在PS4上,我們得到了第一個出在次世代平台的恐怖遊戲的機會。最終因為Epic有提供很好的跨平台解決方案,這讓我們省去了很大的功夫。

一段時間後


回想在2013年9月,我們在PC上的銷售情況並不如我們所預期,但主要是因為我們對PC市場不夠了解,事實上大多數玩家會在遊戲打折購入。過了第一個月後,只有116,000套賣出,但在1月時銷售量達到了328,000套。過了一年的銷售後,大約有600,000套的遊戲在PC平台售出,平均售價約$12鎂,原始售價是$19.99鎂。這個數量讓我們可以衝到Steam第一面的暢銷排行榜。

到了今天,大約有3百萬套的主程式及DLC在所有平台被下載。我們非常幸運,這結果比所期望的目標還要好很多。這對我們來說不是一趟簡單的旅程,經過尋找資金、及全團隊共同努力製作遊戲,我們現在可以說任務完成了。我們創造了全新的IP及全新的團隊並且是完全獨立的。有了這次的營收,我們現在有能力去製作及獨立發行目前正在進行中的Outlast 2。

我們的奮戰因Outlast的銷售而成功了,我們不會停留在這次的成功。當然也必須留下Outlast的真正DNA,及玩家真的愛上這個遊戲的地方。但是我們想要促使團隊儘力去做出最佳的恐怖遊。這一次,我們的團隊在第一天就已經準備好了,而且開發流程也確定了,接下來可以專注於遊戲內容的開發。

Outlast的製作看來已經邁入終點,現在我們必須去思考這個遊戲系列以及公司的中長期目標。

在品牌系列上,首先我們必須先決定Outlast 2是否要接著Outlast的故事繼續發展。因為我們是做恐怖遊戲,我們不希望失去開始製作Outlast那種令人驚訝的感覺。Whistleblower有點喪失了那種在未知的世界探索的感覺,因為玩家對瘋人院已經熟悉了。我們決定製作Outlast 2是相同的世界觀,但是有著不同的設定及不同的角色。

我們也在評估是否要將Outlast放到不同的媒體上,這可以幫助我們拓展客群。

在公司經營上,我們希望能保留獨立開發並且不用去擔心資金不足的問題。Outlast 2會有較大的開發成本,但是我們會持續下決策來確保公司能負擔團隊的成長。

同時我們也決定要去創造一個適合團隊開發的工作環境。這也是個非常困難的挑戰,因為這需要的技能和做遊戲非常不一樣。在Outlast,我們只有專注在把遊戲推出,並沒有人去規劃、管理及其它做遊戲不同的事務。所以我們雇用了一個製作人,Anne Gibeault,幫助我們的團隊及公司成長。

這是一段我們創造自己團隊及遊戲的旅程。我們希望你對這段旅程的經過有興趣。

過了16年後,我感到很幸運能繼續製作遊戲及娛樂玩家。我希望未來還能有很長一段時間能持續做遊戲。

January 29, 2015 | By Philippe Morin

2017年5月19日 星期五

[翻譯]Outlast是如何誕生的(上)



原文出自Horror in the Making: How Red Barrels outlasted Outlast

Philippe Morin是Red Barrels的共同創辦人之一。

這篇文章描述製作Outlast的製作過程及Red Barrels的誕生。這是條充滿著痛苦及困難的道路,但是我們沒有後悔。如果能夠再次回到過去,我們不會想改變這次經驗。


堅持是成功之母


在Uncharted: Drake’s Fortune上市之後,我回去Ubisoft Montreal做創意總監。我當時在Naughty Dog學到了很多的事情,並且渴望將它投入Ubisoft 的作品。因為這兩間公司有著非常不同的設計理念及製作流程,我發現只有兩個方向可以選擇,一個是把Ubisoft完全改造,另一個是離開Ubisoft,建立自己的團隊。

在2009年,我選擇了去做點新的嘗試並且再次離開Ubisoft。我加入了EA Montreal,製作全新的產品。原始概念由Hugo Dallaire(Splinter Cell and Army of Two的前美術總監)負責。我被他的概念吸引,並藉著這個機會在這小團隊內開始新的嘗試。

幾個月後,David Chateauneuf加入了這個團隊。我在Donald Duck、波斯王子時之沙、刺客教條1和他共事過。

我們覺得這個專案非常有趣,但是EA的經理和我們看法不同。一年之內,EA在經過了幾次的專案的更動後,他們決定將這專案中止。我們仍然相信這個專案會是個超級大作,只是我們在錯的地點及錯的時間。

我們被要求加入另一個在開發中的專案。我對新的專案沒有多少信心。也許是一時衝動,在新的一年開始時我決定離職,Hugo及David也是。

我們彼此意氣相投,有著相同的抱負。開始了我們的公司。

我認為聚齊了成功的條件及勇氣、衝勁,我相信我能發揮出更多的價值。

我們立即開始規劃美術概念設計,認為這會花掉幾個月的時間來產出第一個展示影片,然後再花二個月時目來取得發行合約。但最後這花了我們18個月。

而且這只是個開始。

Outlast的誕生


所以我們準備去做什麼東西?第一件事我們要去決定遊戲的類型,我們列出來一串的想法,理所當然的大部份都很鳥。一段時間過後,我們認為恐怖遊戲是最有吸引力的選擇。

David是個恐怖遊戲愛好者、甚至可以說是專家。在2008年他和我曾試圖建議Ubisoft Montreal製作恐怖遊戲,我們被告知恐怖遊沒有足夠的市場。思考這點後,我們認為現在不用擔心這問題,因為Outlast是用小成本小團隊去開發的,而不是像刺客教條製作,不用擔心市場不夠。

Hugo建議我們可以參考電影"Rubber Johnny"(恐怖連結注意)來製作遊戲。我們立即同意這是個非常好的點子。

於是我們確定了,工作室的第一個專案就是恐怖遊戲。我們過去習得的專業知識可以開始應用在新的公司上。我們非常的興奮及渴望迎接新的挑戰。

各式設計問題突然湧出:

核心的遊戲玩法是什麼?
主角是誰?
如何調整夜視模式與整個美術取向?
故事是什麼?

所有這些問題都須要答案,因為我們決定這專案團隊不超過十個人,這些答案必須考慮到團隊的大小。

第一項決議是關於我們的核心遊戲玩法,我們原先考慮惡靈古堡風格加上有限的彈藥、或者Amnesia的無戰鬥玩法。最後決定採無戰鬥玩法,因為這樣可以讓我們更專注在遊戲體驗上。後續我們仍然有想加上戰鬥,但是在PAX上收到的意見表示,我們不需要戰鬥部份。

我們決定要使用夜視模式後,我們的主角必須有這項需求,我們考慮過SWAT小隊有這項配備,但是已經決定無戰鬥模式,所以我們捨棄武裝人員作為主角。這段時間我們看到了不少有用到夜視模式的電影。"為什麼遊戲不可以?",我們這樣想,並且攝影機也具有這項功能,所以道具就這樣定了。

我們反問自己誰會用到攝影機呢?當我們在腦力激盪關於英雄的旅程時,我們得到了答案。

我們很喜歡Half Life開頭的單純表現。一陀狗屎剛好丟到風扇上,你立刻會想閃避。

我們還必須找出一個玩家想逃離的場所,而且必須是小團隊可以做出來的。有了這些限制才會激發出創新的點子,我們再次的列出所有點子及選項,最後找出了精神病院。精神病院和攝影機一樣在電影中常出現,但在遊戲卻沒有,尤其是寫實型遊戲。我們覺得精神病院是個很好的題材來創造獨特及吸引人的特色,玩家可以去體驗他的故事。

夜視攝影機、精神病院、無戰鬥玩法,這是個很好的開始。經過了幾次的腦力激盪,我們決定了記者的角色。一個記者不會擁有戰鬥技能,也有很好的理由去拿一台攝影機,並且也有探索、採訪的動機。

雖然記者的探索動機很強烈,但我們仍花了一番功夫將它和恐怖遊戲結合。相似的問題總是存在:"我們如何驅使記者去探索,而不是嚇的逃跑?"我們決定讓故事細節與遊戲機制分開,玩家可以決定他們要探索的程度,而不是用遊戲機製強迫玩家看劇情。這方法沒有收到任何負面的回饋,所以我認為這是個好決定。

我們開工做Trailer前最後一件需要決定的事就是嚇人的怪物,他們必須是夠恐怖的。什麼東西能使一個角色嚇人是非常主觀的。有些人認為這取決於外觀、有些覺得是心理、動作等。我們決定採用發瘋的精神病患,這表示我們可以用談話來營造恐怖的氣氛,像是和漢尼拔醫生待在同個小房間一樣。最後我們仍然覺得長相正常的一般人類無法嚇到玩家。

我們針對1970年前MKUltra program及其人體實驗相關做了一番研究。

在那個年代,成立一個精神病院對病患做私人研究並不全然是幻想。在我們的故事中,這些實驗會造成突變及恐怖的負面效果。我們覺得這些病毒會造成死亡,我們想要些不同的效果。

我們與編劇JT Petty一同想了些新的點子,包含私人研究包含細胞突變、睡眠療法、生物技術的奈米機器。我們外行人的想像細胞突變的理論主體,細胞變異造成的機制,及相同的細胞可以造出各種器宮、物種等。

20世紀的犯罪影響了Outlastd一大部份主題,尤其是科技是如何超越自身能力,及經由我們的科技所投射出未知的怪物。

我們已經有了所有我們需要的要素。下一步驟就是如何找尋最佳的方法賣出這些點子。

因為Hugo是真正的Unreal大師,我們決定做個假的遊玩展示影片。主題是模擬玩家遊玩片段。David做了影片設計,Hugo著手視覺呈現,而我專注於故事的寫作及音樂。

我們找了外包幫助我們完成這個展示影片。一個負責角色模型、另一個做動畫、第三個是處理音效。

最後,在2011年6月,過了3個月的努力。影片完成了。有了這個影片、及投影片、設計文件,我們開始旅程及尋找資金。

Show me the money


我們第一個想法是找到間發行商來支持我們。我們花了整個夏天與發行商開會,和他們的開會時間雖然不長,但是等待他們回覆卻很花時間,有可能是數週,甚至一個月。大多數的發行商對這專案很有興趣,但是沒有人拿出資金來投資。我們有談到在Valve的發行權利,但他們不打算投資這個專案,所以我們沒有和他們簽約。我們希望之後能找到一個發行商談成獨佔合約。

我們遇到的很多人並不是十分認同我們的想法,認同我所說這是個3A等級的遊戲。Outlast並不是手機遊戲可以在低成本下完成,我們預計需要1,500,000鎂去雇用最佳的開發者來完成它。

到了2011年11月,我們仍然沒有談到合約,但我們也不是沒有閒著。當我們邊製做影片的同時,我們找到了Canada Media Fund (CMF)加拿大媒體投資。這個投資計畫可以給我們最高1,000,000鎂的投資。這需要花費大量的紙本作業,但是考量到這金額,我們覺得是合理的花費。CMF每年會有2次開放申請,而下次的截止時間就是11月底。這有個問題,CMF其中一項條件是有取得發行合約,若沒有發行合約其它的專案會贏得這個權利,而我們將會失去這次機會。

我們提交出申請時,我們只有SONY對這專案有興趣的信件,及他們的公開募資計劃。我們沒有拿到發行合約。更重要的是,CMF不會投資超過我們75%以上的資金,所以若要取得1,000,000的資金,我們至少先有333,333的資金,但那時並沒有。我們抱著希望提交出申請並開始了數週的等待。

開始等待CMF的回信。這個會花費2個月去得到回信,同時我們開始製作可以遊玩的版本,並讓期望它可以讓我們得到SONY的公開投資。

在聖誕節前的數週,我們收到了CMF的消息。這申請受到了拒絕,而我們等到假期後才知道正式的回覆。不久後,我們知道SONY的公開募資計畫將專注於Vita上,所以我們走到了不幸的結局。

這是我們最低潮的時期,我們已經辭去了工作一年。我們被這狀況完全困住了。12個月沒有了收入、沒有發行商、沒有資金、什麼都沒有。這是最淒涼的時期。

浴火重生


這時間我們開始去認真思考有關於放棄這個專案,回頭去城裡的工作室找份工作,但我們決定先等待CMF的回覆之後在做決定。

在2012年的1月,我們的專案在CMF得到了很高的評價,但因為沒有得到發行合約及不完整的資金狀況被扣了很多的分數。簡單的說,這是技術上的失分,而不是商品上的失分。

下次的申請時間是2012年的4月,在這時間我們必須去取得發行合約及籌得333,333的資金。這表示不論我們是否能取得資金,我們必須要再次渡過沒有薪水的6個月的時間。我們必須下決心去再次嘗試。

因為去年2011年7月和Valve談過的發行合約,我們最少能夠安心的取得發行在Steam上的合約。我們開始看我們三人能湊出多少的現金。這表示了我們必須花掉所有的儲蓄、所有的信用,把我們的房子拿去抵押,並向所有的親戚募款。最終募到了360,000,我們有足夠的資金滿足CMF。

由於我們有了可玩的版本,我們把它送往了Valve,過了幾天之後取得了發行合約。我們同時也Game On Audio簽了合約,負責處理所有的音效設計及語音。這份合約將部份的費用換成產品上市的利潤。我們同時也和音樂製作、腳本製作簽下類似合約。

Walker 早期概念圖(圖)

這個時間點,我們湊齊了CMF投資所需要的要素。

在申請期限之前,我們收到了為數不小的私人投資合約,這允許我們可以雇用更多人並且給予我們在相同於開始這專案前的薪水。最後我們選擇保持獨立,維持我們原來的製作團隊規模。

所以在2012年五月,我們第二次申請了CMF,並且等到六月底去等待回覆。

在我們收到回覆之前,另一個機會出現了。有一家發行商向我們提出邀請,希望我們能展示遊戲。他們正在尋找能簽下的遊戲工作室,因為我們當時並不確定是否能從CMF拿到資金,所以便向他們介紹我們的遊戲。但這份合約的問題是在於恐怖遊戲的題材及所有展示的點子都將屬於發行商,不管我們是否是做他們的遊戲。我們擔心這會衝擊到遊戲的green light。我們在這件事掙扎了一陣子,最終還是選擇不接受。因為這感覺我們像之前工作室下工作。我們放棄了這個機會,再次等待CMF回覆給我們消息。

在2012年六月底,我們收到了CMF的回覆,申請成功了。

我用盡所有的力氣大聲歡呼,我們終於可以做我們自己的遊戲了。

在短暫的慶祝後,我們希望遊戲在新的次世代主機出現之前在PC上架,我們擔心新的主機衝擊我們遊戲。這表示我們只有14個月能做遊戲,同時也是我們所有資金花完的時間。1,400,000並不是一筆小數目,但在我們雇了10個人並支付合作廠商薪水後,很快就花完了。

由於從一開始我們就打算雇用最好的人材,一流人材才可以打造一流的遊戲。我們很樂意減少自身的薪水來雇用他們。我們不希望因為薪水而放掉優秀的人材,所以我們必須確定我們有足夠的資源。

在由CMF拿資金之前,還有許多的文件需要處理,像是股東合約、準備公司銀行帳戶及提供薪水,但我們的時程有限,我們沒有等到完成這些事,就先用自己的錢來雇用員工了。

2017年4月8日 星期六

GOAP

GOAP:Goal-Oriented Action Planning

資料來源

在一般的AI邏輯中,會使用FSM(finite state machine)來作處理
FSM

以下圖顯示



有數個state相互聯結,這些聯結稱為transition。
在執行此AI時,會將transition設定條件,滿足條件就經過transition到另一個state。
這個架構的缺點是,當state變多,整個結構會變複雜,而transition的條件要整體檢視也十分麻煩。

這次介紹的是Goal-Oriented Action Planning
目標導向動作規劃(我是這樣翻)
分成三個部分來說明。
goal:目標
action:動作
planner:規劃器

action:動作

有三個屬性
cost:這個action造成的花費
precondition:這個action執行的前提,類似fsm的state
effects:這個action執行後的效果,類似fsm的state

action定義這個ai所有可以執行的動作。

goal:目標

這個AI要達成的目標。
目標會是某個動作的effects

planner:規劃器

有一個屬性
world state:世界狀態
基本上這個狀態會是action的一個前提

規劃器會從目前所有的動作,一個一個拉出來檢視。
目前的世界狀態是否符合這個動作的前提。
如果不符合,找下一個。
如果符合,檢視effect達到目標?

確認這個達到目標的action組合是最低cost。

範例

現在我試著依照GOAP架構砍柴AI。
這邊已經簡化,不是上方的例子。

目標:
將柴放回倉庫

動作:
(花費) [前提] 效果

取得斧頭


(2) [身上沒斧頭] 身上有斧頭

砍樹


(4) [身上有斧頭] 取得柴

身上滿了


(3) [取得柴] 將柴放回倉庫

撿柴


(8) [天氣晴朗] 取得柴


規劃器
世界狀態
[身上沒有斧頭]
[身上有斧頭]
[取得柴]
[天氣晴朗]

執行的歷程如下
目標:將柴放回倉庫
起始的世界狀態:[身上沒有斧頭]
將所有動作抓出來跑,直到符合。
砍樹:世界狀態不符合此動作的前提。跳過。
身上滿了:世界狀態不符合此動作的前提。跳過。
取得斧頭:符合,世界狀態改為[身上有斧頭]

再跑一次全部的動作,直到符合。
身上滿了:世界狀態不符合此動作的前提。跳過。
砍樹:符合,世界狀態改為[取得柴]

再跑一次全部的動作,直到符合。
將柴放回倉庫:符合,達到目標。



2017年1月11日 星期三

GDC摘要:Making Weird Games For Weird People

這是橫尾太郎在GDC上分享關於遊戲設計中,故事編排的技巧。
我嘗試著截取重點與大家分享。



橫尾太郎是復仇龍騎士尼爾遊戲的編劇以及願君勿死》(君死ニタマフ事ナカレ,願君多珍重)的原作。


橫尾說他之前聽過一些課程關於如何設計角色,但是他總是無法抓到這其中的精髓。於是他
想到兩個技巧

1.backwards scriptwriting
反向腳本撰寫

2.photo thinking
相片思考

Backwards Scriptwriting


將一個故事由開始至結束,用段落分開,以時間排序。
我們回得到一個story Flow

(我用google繪圖弄的)

Emotional Peak情感峰值
指的是在段落推動玩家情緒到最大狀態,讓人悲傷,讓人感動,讓人感到驚奇的段落。

為了方便說明,將這個置換為「那女孩死了。」

但是只有這麼簡單的「這個女孩死了」,並不能催化人的情緒,並不讓人感同身受。
因為沒有the reason emotions are formed情緒形成的原因


舉個例子:

「你的狗死了。」
「某個角色的狗死了」

第一句比第二句帶來更多的情緒。
如果你有養狗,你會想到當時你們一起生活的點滴,一起出去玩的回憶。如果你的狗也過世了,這句話能讓你想起:當牠的生命走到盡頭時,你的情緒是多麼悲傷。

這就是情緒形成的原因。

回到女孩的例子,我現在為它加上原因。

「那女孩死了」
「她還那麼年輕」
「她是個啞巴,不會說話」
「她待人和善,非常親切體貼」
「在她夢寐以求的婚禮上」

再將這些排序一下,對應至story flow,就形成了造成情緒的原因。




情緒是可以被驅動的,只要你給出足夠的理由。這些理由是從你的生活經驗去尋找。很久沒見到的朋友,最後聽到的消息是他已經過世了,而你為此感到後悔。
為什麼呢?因為最後你們是因為誤會而疏遠,也許你想向他道歉。

在story flow中,會有許多峰值與理由,最後十分複雜難以消化。
也就是後續會用到的相片思考



Photo Thinking


這是用來將你腦內的敘述描繪出的一種方式。

我們將要描繪這個女孩的死亡。

「女孩的死亡」

在她的婚禮之中

腹部被刺

在一個異國文化的地方

她戴著面具

但是在這些敘述中,仍然會缺失一些東西。他們的背景是什麼?他們的反應是什麼?於是我將這個場景畫了下來。nier的截圖

根據這樣的圖,我們可以看到王子在他身邊,抱著她痛哭,也許會對她說話。

這就是photo thinking
題外話:有本書叫做The Memory Palace of Matteo Ricci,是介紹記憶術,把大腦想像成一個宮殿,你在內部擺放你想記憶的東西,這是一種以圖像化方式記憶的技巧。

作為這個技巧延伸,我可以畫出關於這個城市,王子以及其他人的反應。

再回到story flow上。

會了要表現出這個emotional peak「女孩的死」,我可以將這些後續的反應,放置到story flow內。

例如灰暗的天空,無人的城鎮瀰漫悲傷,最後畫面落在女孩嚥下最後一口氣。拉遠,只有王子與女孩。


Cause Emotion Store


雖然我是個遊戲製作者,但是遊戲或是故事並非我的目標。我的目標是cause emotion store,在玩家心中留下情緒儲存。在遊戲結束之後,玩家仍會感受到情緒。像是電影與小說。

不過我相信遊戲有潛力達成電影與小說做不到的事,只是我們還未探索到這個方法。

所以我在尼爾的結局中,我假設你們都玩過。

在故事的最後,為了拯救這個重要女角色,你要犧牲一切來換取她的存活。如果選擇這麼做的話,你會消失,世界上沒有你的存在,你的朋友、家人會遺忘你。

然後遊戲就會一頁一頁慢慢地將你的紀錄刪除,留下空白。

我覺得這是能讓玩家留下情緒的一種方式。












UFPS介紹-1