حزمة الوحدة التي تمثل مكتبة C# التفاعلية ومجموعة من الأدوات لتطوير الوحدة.
تتكون مجموعة الأدوات هذه من عدة أجزاء:
Zergrush.Reactive هي مكتبة تفاعلية مصممة لتكون بديلاً أفضل لـ UNIRX. الهدف الرئيسي هو إدارة التبعات المنطقية المعقدة لحالة اللعبة/واجهة المستخدم وجميع أنواع "المحفوظات الحكومية". يتكون من الأجزاء التالية:
zergrush.codegen هي أدوات توليد الرموز التي تسمح بإضافة مجموعة ضخمة من الوظائف المختلفة لنماذج البيانات الخاصة بك ، على سبيل المثال يمكن أن تضيف:
Zergrush.Utils مجموعات من أدوات variuos لمهام Gamedev الشائعة والتمديدات للمكتبات أعلاه.
الآن عملية التثبيت هي ما يلي:
استخدم Package Manager> + (زر)> إضافة حزمة من عنوان URL GIT ...
إذا لم يكن لديك newtonsoft.json-for-unity في مشروعك أولاً ، قم بتثبيت هذا:
https://github.com/jilleJr/Newtonsoft.Json-for-Unity.git#upm
ثم قم بتثبيت هذه الحزمة باستخدام عنوان URL:
https://github.com/CeleriedAway/ZergRush.git
الجزء الأول من Zergrush.Reactive هو الأحداث
public interface IEventStream < out T > : IEventStream
{
IDisposable Subscribe ( Action < T > callback ) ;
}
كما ترى ، فإنه يمثل حدثًا بسيطًا يمكنك الاشتراك فيه للحصول على تحديثاته. عادةً ما لا يتم استدعاء إجراءاتك أثناء عملية التحويلات. يتيح أحداث التنفيذ الرئيسية أحداث "إرسال ()".
طريقة الاشتراك إرجاع كائن "اتصال" قابلة للاتصال. إذا تم التخلص منها ، فإن هذه الاتصالات لم تعد محفورة بعد الآن ولن يتم استدعاء رد الاتصال الأولي بعد ذلك. واحدة من أفضل ما في هذا المجال هو جمع كائنات الاتصال للتخلص تلقائيًا ، لذلك لن تنسى "إلغاء الاشتراك"
class DogModel
{
EventStream injured ;
EventStream < string > spokeSomething ;
}
class MyView : ConnectableMonoBehaviour
{
void Start ( )
{
// those connections is just a list of IDisposables that will be auto-disposed in OnDestroy callback
connections += dogModel . injured . Subscribe ( ShowBloodSpashes ) ;
// Subscribe extension allowing pass connections as first argument
dogModel . spokeSomething . Subscribe ( connections , ShowTextBubble ) ;
}
void ShowBloodSpashes ( ) { .. . }
void ShowTetBubble ( string text ) { .. . }
}
يمكن ترشيح الأحداث وتحويلها ودمجها. مثله..
// The only real stream allowing to send values
EventStream < int > streamOfNumbers = new EventStream < int > ( ) ;
// Subscribe to this stream and you will receive only event number events sent with streamOfNumbers.
IEventStream < int > streamOfEvenNumbers = streamOfNumbers . Filter ( i => IsEven ( i ) ) ;
// Same with odd numbers.
IEventStream < int > streamOfOddNumbers = streamOfNumbers . Filter ( i => IsOdd ( i ) ) ;
// Here you will receive strings created from numbers
IEventStream < string > streamOfSomeStrings = streamOfEvenNumbers . Map ( i => i . ToString ( ) ) ;
// Here you will receive events from both streams
IEventStream < int > mergedStreamOfNumbers = streamOfEvenNumbers . MergeWith ( streamOfOddNumbers ) ;
لكن الجزء الأكثر فائدة من المكتبة في تطوير اللعبة هو الخلية
public interface ICell < out T >
{
IDisposable ListenUpdates ( Action < T > reaction ) ;
T value { get ; }
}
واجهة برمجة التطبيقات الأساسية للخلايا هي:
var moneyCount = new Cell < int > ( ) ;
moneyCount . ListenUpdates ( v => Debug . Log ( $" Money changed to { v } " ) ) ;
moneyCount . value = 10 ;
// It is important that all cell api guerantee that setting same value won't trigger update callback
moneyCount . value = 10 ; // Log won't be trigered
var imRich = moneyCount . Select ( m => m > 100 ) ;
إنه يمثل قيمة تتغير في الوقت المناسب. وتناسب مثالي لتمثيل بيانات اللعبة.
class Item
{
public Cell < int > hpBonus ;
public void Upgrade ( ) => hpBonus . value ++ ;
}
class Weapon
{
public Cell < int > damage ;
}
partial class PlayerData
{
public Cell < int > money ;
public Cell < int > hp ;
public Cell < int > baseMaxHp ;
public void LevelUp ( )
{
baseMaxHp . value ++ ;
hp . value = maxHpTotal . value ;
}
public ReactiveCollection < Item > items ;
public Cell < Weapon > selectedWeapon ;
public void EquipWeapon ( Weapon w ) => selectedWeapon . value = w ;
// Join is the most important operator that transforms ICell<ICell<T>> to just ICell<T>
// That is the core mechanic of cell transformation and
// the one that allows to collapse all dependency layers into one
public ICell < int > damage => selectedWeapon . Map ( w => w . damage ) . Join ( ) ;
// you can use transform api to compose new properties without loosing of its dependancies
// Look how total maxHp is calculated
public ICell < int > maxHpFromItems => items . Map ( i => i . hpBonus ) . ToCellOfCollection ( ) . Map ( itemBuffs => itemBuffs . Sum ( ) ) ;
public ICell < int > maxHpTotal => baseMaxHp . Merge ( maxHpFromItems , ( hp1 , hp2 ) => hp1 + hp2 ) ;
public ICell < float > relativeHp => hp . Merge ( maxHpTotal , ( hp , maxHp ) => hp / ( float ) maxHp ) ;
public ICell < bool > isWounded => relativeHp . Select ( value => value < 0.5f ) ;
}
آمل أن يكون هذا الرمز واضحًا بما يكفي
/*
* To generate code press Shift + Alt + C in unity, or "Code Gen" > "Run CodeGen" from menu
* Some time it is difficult to refactor code because of other generated code
* And new code can be generated only if program is fully compiled, that is IMPORTANT!!!
* Use "Code Gen" > "Generate Stubs" or Shift + Alt + S to generate stub code before or during your refactor
* And when you program is compilable generate code normal way again
*
* Versioning is not supported for BinarySerialization by now
* Json serialization is not very sensitive for versions
*
* Code generation starts with defining tag with task enum value describing which functionality we want to generate
* The simplest one is the following...
*/
[ GenTask (
GenTaskFlags . Serialization | // Fast binary serialize/deserialize methods
GenTaskFlags . JsonSerialization | // Json serialize/deserialize methods
GenTaskFlags . Hash | // Fast hash code calculation
GenTaskFlags . UpdateFrom | // Deep copy optimized for copying into other created similar model
GenTaskFlags . CompareChech | // Function that prints all differences between two models into error log
GenTaskFlags . DefaultConstructor | // Generate Constructor that constructs all class type fields with defaults
GenTaskFlags . PolymorphicConstruction // Allows to save ancestor as base class values as fields or in containers
) ]
// All generated code will be placed into "x_generated" folder
[ GenInLocalFolder ]
public partial class CodeGenSamples : ISerializable
{
// All fields are automatically included
int intField ;
// All properties are not included by default
string stringPropWithoutTagNotIncluded { get ; set ; }
// You need to specify which properties to include with GenInclude tag
[ GenInclude ] string stringProp { get ; set ; }
// You can ignore some fields with GenIgnoreTag
[ GenIgnore ] int someTempIgnoredField ;
// all ref type fields considered not null by default, if null expect exception during generated function calls
string stringFieldMustNotBeNull ;
// Use CanBeNull tag for fields that can be null so code for this case will be generated
[ CanBeNull ] string stringFieldThatCanBeNull ;
// Extension methods for external classes used in generated classes will be generated.
// But extension methods can't access private members, so be careful with that
ExternalClass externalClass ;
Vector3 vector ;
// Other generated objects can be included
[ CanBeNull ] OtherData otherData ;
// You can ignore specific parts of code generation, for example if you do not want default construction of this field
[ GenIgnore ( GenTaskFlags . DefaultConstructor ) ]
OtherData otherData2 ;
List < int > listsOfPrimitivesAreOk ;
List < OtherData > listsOfDataAreOk ;
int [ ] arraysAreOk ;
// Dictionaries are supported but not for deep copy (UpdateFrom) for now...
[ GenIgnore ( GenTaskFlags . UpdateFrom ) ] Dictionary < int , OtherData > dictsAreOk ;
[ GenIgnore ( GenTaskFlags . UpdateFrom ) ] Dictionary < int , List < List < string > > > complexStructuresAreAlsoOk ;
// NOT SUPPORTED
[ GenIgnore ] int [ , ] multyDimArraysAreNotSupported ;
// ZergRush.Reactive primitives are supported
Cell < OtherData > reactiveValue ;
ReactiveCollection < int > reactiveCollections ;
[ GenIgnore ( GenTaskFlags . DefaultConstructor ) ]
public List < CodeGenSamples > ancestorArray = new List < CodeGenSamples >
{
// because of PolymorphicConstruction, Ancestor class will be serialized in right way
new Ancestor ( )
} ;
static void HowToUse ( )
{
var data = new CodeGenSamples ( ) ;
// json serialize
string jsonData = data . SaveToJsonString ( ) ;
// binary serialize
byte [ ] binaryData = data . SaveToBinary ( ) ;
// json deserialize
data = jsonData . LoadFromJsonString < CodeGenSamples > ( ) ;
// binary deserialize
var data2 = binaryData . LoadFromBinary < CodeGenSamples > ( ) ;
// deep copy data2 into data
data . UpdateFrom ( data2 ) ;
// compare data hashes
if ( data . CalculateHash ( ) != data2 . CalculateHash ( ) )
{
// and check for differences if hashes are not equal
data . CompareCheck ( data2 , new Stack < string > ( ) ) ;
}
// polymorphic construction example
var ancestor = CreatePolymorphic ( ( ushort ) Types . Ancestor ) ;
}
}
// All class tags are inhereted, so its handy to create one base class for you model classes with all tags you want
[ GenInLocalFolder ]
public partial class Ancestor : CodeGenSamples
{
public int fields ;
}
[ GenTask ( GenTaskFlags . SimpleDataPack ) ]
[ GenInLocalFolder ]
public partial class OtherData
{
public int someData ;
}
[ GenInLocalFolder ]
public class ExternalClass
{
public int somePublicField ;
// private fields are not included in extension methods generation
int somePrivateField ;
}