إنشاء بطاقات اللعب بشكل ديناميكي باستخدام شيفرة من أجل لعبة تقوم على التكديس
Arabic (العربية/عربي) translation by Maryam Abbas (you can also view the original English article)



يختلف هذا البرنامج التعليمي عن البرامج التعليمية السابقة، حيث يتم توجيه هذا البرنامج نحو اللعبة المكدسة ونماذج اللعب، خاصة ألعاب الورق. سنقوم بإنشاء مجموعة ورق اللعب ثنائية الأبعاد في Unity دون استخدام أي صورة - تمامًا باستخدام الشفرة.
1. مكونات مجموعة بطاقات اللعب
تحتوي مجموعة أوراق اللعب على 52 بطاقة مع 13 بطاقة من كل من الرموز ال 4 المختلفة. من أجل إنشاء رمز واحد ، سنحتاج إلى إنشاء هذه الرموز الأربعة، قاعدة مستطيلة مستديرة للبطاقة، والتصميم على ظهر البطاقة.
يمكن أن يكون التصميم على ظهر البطاقة أي نمط تجريدي، وهناك العديد من الطرق لإنشاء واحد. سنقوم بإنشاء نمط بسيط قابل للترتيب جنباً إلى جنب والذي سيتم بعد ذلك صفه بشكل متجانب لإنشاء التصميم. لن يكون لدينا أي تصميم خاص للبطاقات A و K و Q و J.
2. حلول بديلة
قبل أن نبدأ، يجب أن أذكر أن هناك حلول أسهل يمكننا استخدامها لإنشاء مجموعة من البطاقات. بعض من هذه الحلول مذكورة أدناه.
- والواضح هو استخدام الفن الذي تم تقديمه مسبقًا لجميع التصميمات.
- الأقل وضوحاً هو استخدام خط يحتوي على كافة الرموز الضرورية. يمكننا أيضًا تحويل الخط المذكور إلى خط نقطي لتقليل طلبات ورق اللعب وزيادة الأداء.
الحل القائم على الخطوط هو الأسرع والأسهل إذا كنت تريد عمل نماذج أولية سريعة.
3. إنشاء المحتويات أثناء وقت التشغيل
الخطوة الأولى هي تعلم كيفية إنشاء محتوى ثنائي الابعاد
باستخدام الكود الذي يمكن استخدامه بعد ذلك لإنشاء النقوش
في الوحدة. تعرض التعليمة البرمجية التالية إنشاء بنية فارغة 256 × 256.
Texture2D texture = new Texture2D(256, 256, TextureFormat.ARGB4444, false); texture.filterMode=FilterMode.Trilinear; texture.wrapMode=TextureWrapMode.Clamp; texture.Apply();
الفكرة هي رسم جميع التصاميم على البنية قبل استخدام طريقة التطبيق
. يمكننا رسم التصاميم على البنية بكسل فبكسل باستخدام طريقة إعداد البكسل
، كما هو موضح أدناه.
texture.SetPixel(x, y, Color.white);
على سبيل المثال، إذا أردنا ملء البنية بأكملها بلون، فيمكننا استخدام طريقة كهذه.
private void PaintRectangle(Texture2D texture, Rect rectBounds, Color color) { for (int i=(int)rectBounds.x;i<rectBounds.x+rectBounds.width;i++){ for (int j=(int)rectBounds.y;j<rectBounds.y+rectBounds.height;j++){ texture.SetPixel(i, j, color); } } } // PaintRectangle(texture,new Rect(0,0,256,256),Color.red);
بمجرد إنشاء البنية ثنائية البعد
، يمكننا استخدامها لإنشاء النقوش
ليتم عرضها على الشاشة.
Sprite sprite = Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width, texture.height), new Vector2(0.5f, 0.5f),1);
الجزء المعقد في كل هذا هو خلق التصاميم اللازمة على البنية.
4. خلق شكل القلب
عندما يتعلق الأمر بإنشاء شكل القلب، هناك العديد من الطرق المختلفة التي يمكن أن نستخدمها، من بينها بعض المعادلات المعقدة بالإضافة إلى خلط بسيط للأشكال. سوف نستخدم طريقة خلط الأشكال كما هو موضح أدناه، وتحديدًا بالمثلث.



كما لاحظتم، يمكننا استخدام دائرتين ومربع أو مثلث لخلق شكل القلب الأساسي. هذا يعني أنه سيغيب عن ذلك المنحنيات الجميلة الإضافية ولكنه يناسب هدفنا تمامًا.
تلوين الدائرة
دعونا نحضر بعض المعادلات لرسم دائرة. بالنسبة لدائرة بها مركز في الأصل ونصف قطر r
، تكون معادلة النقطة(x، y)
على الدائرة x2
+
y2
=
r
2
. الآن إذا كان مركز الدائرة في (h، k)
تصبح المعادلة (x 2 +
(y--
h)
k) 2
=
r2
. لذا إذا كان لدينا صندوق مستطيل يحيط بمربع، فيمكننا الالتفاف من خلال جميع النقاط داخل هذا المستطيل وتحديد النقاط التي تقع داخل الدائرة والتي لا تقع فيها. يمكننا بسهولة إنشاء طريقة الدائرة الملونة
استنادًا إلى هذا الفهم، كما هو موضح أدناه.
private void PaintCircle(Texture2D texture,float radius, Vector2 midPoint, Color color){ Rect circleBounds=new Rect(); circleBounds.x=Mathf.Clamp(midPoint.x-(radius),0,resolution); circleBounds.y=Mathf.Clamp(midPoint.y-(radius),0,resolution); circleBounds.width=Mathf.Clamp(2*radius,0,resolution); circleBounds.height=Mathf.Clamp(2*radius,0,resolution); float iValue; for (int i=(int)circleBounds.x;i<circleBounds.x+circleBounds.width;i++){ for (int j=(int)circleBounds.y;j<circleBounds.y+circleBounds.height;j++){ iValue=(Mathf.Sqrt(radius*radius-((j-midPoint.y)*(j-midPoint.y)))); if(i>midPoint.x-iValue&&i<midPoint.x+iValue){ texture.SetPixel(i, j, color); } } } } PaintCircle(texture,radius,mid,Color.red);
حالما تكون لدينا طريقة الدائرة الملونة
، يمكننا المضي قدما في خلق شكل القلب كما هو مبين أدناه.
void PaintHearts(Texture2D texture){ //2 circles on top float radius =resolution*0.26f; Vector2 mid=new Vector2(radius,resolution-radius); PaintCircle(texture,radius,mid,Color.red); mid=new Vector2(resolution-radius,resolution-radius); PaintCircle(texture,radius,mid,Color.red); //triangle at bottom float width=resolution*0.58f; int endJ=(int)(resolution*0.65f); int startJ=(int)(resolution*0.1f); float delta=(width/endJ); float midI=resolution*0.5f; for (int i=0;i<resolution;i++){ for (int j=startJ;j<endJ;j++){ if(i>(midI-(delta*(j-startJ)))&&i<(midI+(delta*(j-startJ)))){ texture.SetPixel(i, j, Color.red); } } } }
الدقة
المتغيرة هي عرض وارتفاع البنية.
5. خلق شكل المعين
سنناقش طريقتين لرسم شكل المعين.
تلوين المعين البسيط
الطريقة الأسهل هي تمديد الشفرة المستخدمة للمثلث وإضافة المثلث المقلوب في الأعلى لإنشاء الشكل اللازم، كما هو موضح أدناه.
void PaintDiamond(Texture2D texture){ float width=resolution*0.35f; for (int i=0;i<resolution;i++){ for (int j=0;j<resolution;j++){ if(ValidDiamondPosition(i,j,width)){ texture.SetPixel(i, j, Color.red); } } } } private bool ValidDiamondPosition(int i, int j, float width) { //i =col j = row bool isValid=false; float midI=(resolution/2.0f); float midJ=(resolution/2.0f); float delta=(width/midJ); if(j>midJ){ j=resolution-j; if(i>(midI-(delta*j))&&i<(midI+(delta*j))){ isValid=true; } }else{ if(i>(midI-(delta*j))&&i<(midI+(delta*j))){ isValid=true; } } return isValid; } PaintDiamond(texture);
تلوين المعين المنحني
والثانية هي استخدام معادلة أخرى لإنشاء نسخة أفضل ومنحنية من شكل المعين لدينا. سوف نستخدم هذه الطريقة لإنشاء تصميم متجانب للجانب الخلفي من بطاقتنا. تستمد معادلة الدائرة من المعادلة الأصلية للقطع الناقص، وهو (
x / a)
2 +
(y /
b) 2 =
r2
.
هذه المعادلة هي نفس تلك الخاصة بالدائرة عندما يكون المتغيران a
و b
كلاهما 1.
يمكن بعد ذلك توسيع معادلة القطع الناقص إلى معادلة قطع ناقص فائق للأشكال المتشابهة فقط عن طريق تغيير القوة ،(
x/a)n + (y/b)
n
=
r
n
. لذلك عندما يكون n
هو 2
لدينا القطع الناقص، وبالنسبة للقيم الأخرى من n
سيكون لدينا أشكال مختلفة، واحدة منها هي المعين الخاص بنا. يمكننا استخدام النهج المستخدم للوصول إلى طريقة الدائرة الملونة
للوصول إلى طريقة المعين الملون
الجديد.
private void PaintDiamond(Texture2D texture, Rect rectBounds, Vector2 midPoint, Color color, float n=0.8f) { float iValue; int a=(int)(rectBounds.width/2); int b=(int)(rectBounds.height/2); float nRoot=1/n; float delta; float partialOne; rectBounds.width=Mathf.Clamp(rectBounds.x+rectBounds.width,0,resolution); rectBounds.height=Mathf.Clamp(rectBounds.y+rectBounds.height,0,resolution); rectBounds.x=Mathf.Clamp(rectBounds.x,0,resolution); rectBounds.y=Mathf.Clamp(rectBounds.y,0,resolution); for (int i=(int)rectBounds.x;i<rectBounds.width;i++){ for (int j=(int)rectBounds.y;j<rectBounds.height;j++){ delta=Mathf.Abs(j-midPoint.y); partialOne=Mathf.Pow(b,n)-Mathf.Pow(delta,n); iValue=((a/b)*Mathf.Pow(partialOne,nRoot));//+mid.x if(i>midPoint.x-iValue && i<midPoint.x+iValue){ texture.SetPixel(i, j, color); } } } }
رسم مستطيل مدور
يمكن استخدام نفس المعادلة لإنشاء الشكل الأساسي لبطاقة المستطيل المدور من خلال تغيير قيمة n.
private void PaintRoundedRectangle(Texture2D texture) { for (int i=0;i<resolution;i++){ for (int j=0;j<resolution;j++){ if(ValidRRPosition(i,j)){ texture.SetPixel(i, j, Color.white); }else{ texture.SetPixel(i, j, Color.clear); } } } } private bool ValidRRPosition(int i, float j) { bool isValid=false; float iValue; Vector2 mid; mid=new Vector2(resolution/2,resolution/2); int radius=(int)(resolution/2); int a=radius; int b=radius; float n=16; float nRoot=1/n; float delta=j-mid.y; float partialOne=Mathf.Pow(b,n)-Mathf.Pow(delta,n); iValue=((a/b)*Mathf.Pow(partialOne,nRoot));//+mid.x if(i>mid.x-iValue && i<mid.x+iValue){ isValid=true; } return isValid; }
تلوين تصميم القرميد
باستخدام طريقة المعين الملون
هذه، يمكن أن نستخلص خمسة معينات لإنشاء بنية القرميد للتصميم على ظهر بطاقتنا.



التصميم القرميدي والجانب الخلفي المبلط من البطاقة
private void PaintTilingDesign(Texture2D texture, int tileResolution) { Vector2 mid=new Vector2(tileResolution/2,tileResolution/2); float size=0.6f*tileResolution; PaintDiamond(texture,new Rect(mid.x-size/2,mid.y-size/2,size,size),mid,Color.red); mid=new Vector2(0,0); PaintDiamond(texture,new Rect(mid.x-size/2,mid.y-size/2,size,size),mid,Color.red); mid=new Vector2(tileResolution,0); PaintDiamond(texture,new Rect(mid.x-size/2,mid.y-size/2,size,size),mid,Color.red); mid=new Vector2(tileResolution,tileResolution); PaintDiamond(texture,new Rect(mid.x-size/2,mid.y-size/2,size,size),mid,Color.red); mid=new Vector2(0,tileResolution); PaintDiamond(texture,new Rect(mid.x-size/2,mid.y-size/2,size,size),mid,Color.red); }
6. إنشاء شكل البستوني
شكل البستوني هو انقلاب عمودي لشكل القلوب لدينا إلى جانب شكل القاعدة. سيكون شكل القاعدة هذا هو نفسه بالنسبة لشكل السباتي أيضًا. يوضح الشكل أدناه كيف يمكننا استخدام دائرتين لإنشاء هذا الشكل الأساسي.



ستكون طريقة البستوني الملون
كما هو موضح أدناه.
void PaintSpades(Texture2D texture){ //2 circles on middle float radius =resolution*0.26f; Vector2 mid=new Vector2(radius,resolution-2.2f*radius); PaintCircle(texture,radius,mid,Color.black); mid=new Vector2(resolution-radius,resolution-2.2f*radius); PaintCircle(texture,radius,mid,Color.black); //triangle at top float width=resolution*0.49f; int startJ=(int)(resolution*0.52f); float delta=(width/(resolution-startJ)); float midI=resolution*0.5f; int alteredJ; radius=resolution*0.5f; float midJ=resolution*0.42f; float iValue; for (int i=0;i<resolution;i++){ //top triangle for (int j=startJ;j<resolution;j++){ alteredJ=resolution-j; if(i>(midI-(delta*alteredJ))&&i<(midI+(delta*alteredJ))){ texture.SetPixel(i, j, Color.black); } } //bottom stalk for (int k=0;k<resolution*0.5f;k++){ mid=new Vector2(0,midJ); iValue=(Mathf.Sqrt(radius*radius-((k-mid.y)*(k-mid.y))));//+mid.x; if(i>mid.x+iValue){ mid=new Vector2(resolution,midJ); iValue=(Mathf.Sqrt(radius*radius-((k-mid.y)*(k-mid.y))));//+mid.x; if(i<mid.x-iValue){ texture.SetPixel(i, k, Color.black); } } } } }
7. إنشاء شكل السباتي
في هذه المرحلة، أنا متأكد من أنه يمكنك معرفة مدى سهولة تكوين شكل السباتي. كل ما نحتاجه هو دائرتين والشكل الأساسي الذي أنشأناه لشكل البستوني.



ستكون طريقة السباتي الملون
كما هي موضحة أدناه.
void PaintClubs(Texture2D texture){ int radius=(int)(resolution*0.24f); //3 circles Vector2 mid=new Vector2(resolution*0.5f,resolution-radius); PaintCircle(texture,radius,mid,Color.black); mid=new Vector2(resolution*0.25f,resolution-(2.5f*radius)); PaintCircle(texture,radius,mid,Color.black); mid=new Vector2(resolution*0.75f,resolution-(2.5f*radius)); PaintCircle(texture,radius,mid,Color.black); //base stalk radius=(int)(resolution*0.5f); float midY=resolution*0.42f; int stalkHeightJ=(int)(resolution*0.65f); float iValue; for (int i=0;i<resolution;i++){ for (int j=0;j<stalkHeightJ;j++){ mid=new Vector2(resolution*-0.035f,midY); iValue=(Mathf.Sqrt(radius*radius-((j-mid.y)*(j-mid.y))));//+mid.x; if(i>mid.x+iValue){ mid=new Vector2(resolution*1.035f,midY); iValue=(Mathf.Sqrt(radius*radius-((j-mid.y)*(j-mid.y))));//+mid.x; if(i<mid.x-iValue){ texture.SetPixel(i, j, Color.black); } } } } }
8. رزم المحتويات
إذا قمت باستكشاف ملفات المصدر Unity لهذا المشروع، فستجد طبقة مدير المحتوى
التي تقوم بكل الحمل الثقيل. وبمجرد إنشاء جميع المحتويات الضرورية، تستخدم طبقة مدير المحتوى
أسلوب رزم المحتويات
لدمجها في محتوى واحد، وبالتالي تقليل عدد استدعاءات السحب المطلوبة عند استخدام هذه الأشكال.
Rect[] packedAssets=packedTexture.PackTextures(allGraphics,1);
باستخدام مصفوفة الاصول المكدسة
، يمكننا استرداد المربعات المحيطة بالمحتويات المستقلة من المحتوى الرئيسي المسمى المحتوى المكدس.
public Rect GetTextureRectByName(string textureName){ textureName=textureName.ToLower(); int textureIndex; Rect textureRect=new Rect(0,0,0,0); if(textureDict.TryGetValue(textureName, out textureIndex)){ textureRect=ConvertUVToTextureCoordinates(packedAssets[textureIndex]); }else{ Debug.Log("no such texture "+textureName); } return textureRect; } private Rect ConvertUVToTextureCoordinates(Rect rect) { return new Rect(rect.x*packedTexture.width, rect.y*packedTexture.height, rect.width*packedTexture.width, rect.height*packedTexture.height ); }
استنتاج
مع كل المكونات الضرورية التي تم إنشاؤها، يمكننا المضي قدمًا في إنشاء مجموعة أوراق اللعب الخاصة بنا، حيث إنها مجرد مسألة وضع الأشكال بشكل صحيح. يمكننا استخدام واجهة Unity UI للبطاقات المركبة أو يمكننا إنشاء البطاقات كزخارف فردية. يمكنك استكشاف نموذج التعليمة البرمجية لفهم كيفية استخدام الطريقة الأولى لإنشاء تخطيطات البطاقة.
يمكننا اتباع نفس الطريقة لإنشاء أي نوع من الفن الديناميكي في زمن التشغيل في Unity. إن إنشاء الفن في زمن التشغيل هو عملية متعطشة للأداء، ولكن لا بد من القيام به مرة واحدة فقط إذا قمنا بحفظ وإعادة استخدام هذه القوام بكفاءة. من خلال التكديس فإن الأصول التي تم إنشاؤها ديناميكيًا في نسيج واحد، فإننا نكتسب أيضًا مزايا استخدام بنية تماثيل .
الآن وبعد أن أصبح لدينا بطاقات لعب الورق، أخبرني ما هي الألعاب التي تخطط لإنشائها بها.