تأثيرات Sprite والنص

الظل، والحد الخارجي، والتوهج، والتمويه على sprites ثنائية الأبعاد وعلى النص، مع تكلفة كل منها، وما يفعله بـsprite متحرك، والوصفة اليدوية التي يحل محلها.

ما الغاية من هذا#

حتى الآن كان بإمكان sprite ثنائي الأبعاد أن يحمل تلوينًا (tint) وصورة، لا غير. لرفع شخصية عن خلفيتها كان عليك بناء التأثير يدويًا: أنشئ sprite ثانيًا، لوِّنه أسود، أزِحه، ضعه خلفه، وأبقِه متزامنًا مع الأول في كل إطار. إنها تعمل. لكنها أيضًا أربعة أسطر إضافية من المحاسبة لكل شخصية، واليوم الذي تنسى فيه واحدًا منها يتخلف الظل عن صاحبه.

أربعة أوامر تفعل ذلك نيابة عنك، على sprites وعلى النص:

hero = LoadSprite("hero.png")
SpritePosition(hero, 160, 120)

SpriteShadow(hero, 4, 4, 6, 128)        ' offset x, offset y, softness, opacity
SpriteOutline(hero, 2, 255, 255, 255)   ' thickness, colour
SpriteGlow(hero, 0, 240, 255, 8)        ' colour, size

DO
  Sync()
LOOP

لا شيء آخر للحفاظ عليه. حرِّك sprite، أو غيِّر مقياسه، أو اقلبه، أو شغِّل رسومه المتحركة: تتبعه التأثيرات.

التأثيرات الأربعة#

Shadow#

SpriteShadow(spriteID, offsetX, offsetY, softness, opacity)
SpriteShadowColor(spriteID, red, green, blue)

offsetX وoffsetY بوحدات شاشتك الخاصة، تُحسَب بالطريقة نفسها التي يُحسَب بها SpritePosition: Y الموجب هو للأسفل. تنشر softness الظل؛ وتتراوح opacity من 0 إلى 255. اللون أسود ما لم تقل غير ذلك.

SpriteShadow(hero, 3, 5, 4, 110)
SpriteShadowColor(hero, 20, 0, 40)      ' a cold shadow, not a grey one

ظل بعتامة 0 ليس ظلًا. هذه هي طريقة إيقافه.

Outline#

SpriteOutline(spriteID, thickness, red, green, blue)

يتبع الحد الخارجي (outline) ظل الشكل (silhouette) لـsprite، لا المستطيل الذي يعيش فيه: ملف PNG شفاف يحصل على حد خارجي حول الرسمة، لا حول الملف. السماكة بوحدات الشاشة، والمدى المفيد من 1 إلى 3. فوق ذلك تبدأ الاتجاهات الثمانية التي يُبنى منها الحد الخارجي بالظهور كثمانية فصوص (lobes).

SpriteOutline(hero, 2, 255, 255, 255)   ' the readable-on-anything outline
SpriteOutline(hero, 0, 0, 0, 0)         ' off

Glow#

SpriteGlow(spriteID, red, green, blue, size)

هالة ملوَّنة، تنتشر size وحدة بعد ظل الشكل. تقع خلف sprite وأمام الظل، وهذا ما يتيح لك استخدام الاثنين معًا.

SpriteGlow(pickup, 255, 210, 0, 10)     ' this one is worth walking to

Blur#

SpriteBlur(spriteID, radius)

التمويه (blur) هو الاستثناء. تُرسَم الثلاثة الأخرى حول sprite؛ بينما يستبدل التمويه صورة sprite نفسها بنسخة مموَّهة منها. هذا يعني شيئين يستحقان المعرفة:

  • لا يكلِّف شيئًا لكل إطار (تُحسَب الصورة المموَّهة مرة واحدة)، و
  • يُقصّ التمويه عند حافة الصورة. رسمة تلامس حافة ملف PNG الخاص بها تفقد الجزء من هالتها الذي كان سيقع خارجها. اترك بضعة بكسلات شفافة كهامش في الملف إن كنت تخطط لتمويهه.

يستمر SpriteColor في العمل على sprite مموَّه.

إيقاف كل شيء، وإعادته#

SpriteEffects(spriteID, 0)   ' all four off
SpriteEffects(spriteID, 1)   ' all four back, exactly as they were

لا يمحو SpriteEffects إعداداتك. إنه مفتاح تبديل، لا إعادة ضبط، لذا يمكن لشخصية أن تومض دون أن تضطر إلى إعادة تعريف ظلها بعد ذلك.

الأربعة نفسها على النص#

score = CreateText("SCORE 0")
TextPositionSet(score, 10, 10)
TextSizeSet(score, 6)

TextOutline(score, 3, 0, 0, 0)          ' readable on any background
TextShadow(score, 2, 2, 4, 160)
TextGlow(score, 255, 80, 0, 6)
TextBlur(score, 0)
TextEffects(score, 1)

تطابق التوقيعات (signatures) توقيعات sprite تمامًا، لذا لا شيء جديدًا لتعلُّمه. اختلافان في السلوك:

  • يفوز التوهج (glow) بنعومة (softness) الظل. يُرسَم كلاهما بالآلية نفسها، والتوهج بلا نعومة غير مرئي بينما الظل بلا نعومة يبقى ظلًا. عندما تطلب الاثنين، يحصل التوهج على النعومة.
  • تنطبق هذه على النص المتجهي، لا على نص خط البت مان (bitmap-font). النص المرسوم بـTextFontImage تركيبة من sprites؛ تُتجاهَل الأوامر عليه بصمت. استخدم تأثيرات sprite على الـsprites في هذه الحالة.

مثال عملي: مقروء على أي شيء#

المشكلة التي تحلها هذه الأوامر فعليًا هي تباين لا تتحكم فيه. نتيجة بيضاء غير مرئية على الثلج؛ ونتيجة سوداء غير مرئية ليلًا. يحلها حد خارجي واحد مرة واحدة، لكل خلفية سترسمها على الإطلاق.

CreateText(1, "SCORE 0")
TextPositionSet(1, 10, 10)
TextSizeSet(1, 8)
TextColorSet(1, 255, 255, 255, 255)
TextOutline(1, 3, 0, 0, 0)

hero = LoadSprite("hero.png")
SpriteName(hero, "hero")
SpriteShadow(hero, 4, 6, 5, 120)
SpriteOutline(hero, 2, 255, 255, 255)

points = 0
DO
  points = points + 1
  TextString(1, "SCORE " + Str(points))
  Sync()
LOOP

قراءة تأثير مرة أخرى#

لكل معامل تكتبه قارئ، بالطريقة نفسها التي يملك بها SpriteColor SpriteColorRed. هذه قاعدة اللهجة، وتتبعها هذه الأوامر.

IF SpriteEffectsEnabled(hero) = 1
  SpriteEffects(hero, 0)
ENDIF

alpha = SpriteShadowAlpha(hero)      ' 0 to 255
size  = SpriteGlowSize(hero)         ' 0 means no glow

المجموعة الكاملة، لكل عائلة: SpriteShadowOffsetX، وSpriteShadowOffsetY، وSpriteShadowBlur، وSpriteShadowAlpha، وSpriteShadowColorRed، وSpriteShadowColorGreen، وSpriteShadowColorBlue، وSpriteGlowRed، وSpriteGlowGreen، وSpriteGlowBlue، وSpriteGlowSize، وSpriteOutlineThickness، وSpriteOutlineRed، وSpriteOutlineGreen، وSpriteOutlineBlue، وSpriteBlurRadius، وSpriteEffectsEnabled، إضافةً إلى السبعة عشر نفسها بسابقة Text.

قاعدتان تستحقان المعرفة:

  • sprite لم يُعطَ تأثيرًا قط يُبلِّغ عن نفسه أنه مُفعَّل (ON). هذه هي الحالة الافتراضية؛ الإجابة بـ0 كانت ستخبر البرنامج أن تأثيراته مقطوعة قبل أن يضبط أيًّا منها.
  • السؤال عن sprite غير موجود يجيب بـ0، ولا يوقف البرنامج. هذه هي طريقة معاملة اللهجة للأسئلة في كل مكان: يجيب SpriteHit بـ0 عندما لا يُلمَس شيء، وتقرأ البرامج ذلك الصفر دون اختباره.

ما يكلِّفه#

الظل، والحد الخارجي، والتوهج تُرسَم، لذا تكلِّف استدعاءات رسم (draw calls): استدعاء واحد للظل (ثلاثة عندما يكون ناعمًا)، وأربعة للتوهج، وثمانية للحد الخارجي. شاشة بها مئتا sprite بحد خارجي هي شاشة ترسم ألفًا وثمانمئة رباعية، وسيظهر ذلك. إنها مخصصة لحفنة الأشياء التي يُفترض أن ينظر إليها اللاعب: الشخصية، والعنصر القابل للالتقاط، والوحدة المحدَّدة.

لا يكلِّف التمويه شيئًا لكل إطار.

قاعدة عملية: تأثيرات على ما يتحرك ويهم، ولا شيء على الخلفية.

ما ينتقل إلى لعبة مُصدَّرة#

الهدفShadow وoutline وglowBlurتأثيرات النص
المحررنعمنعمنعم
تصدير HTML5نعمنعمنعم
Native وwasm (Bevy)نعملالا

يُبنى Shadow وoutline وglow من sprites عادية، لذا تسافر إلى أينما تسافر sprites. يحتاج Blur وتأثيرات النص إلى جسر لا تملكه أهداف Bevy بعد: يذكرها تقرير تحويل التصدير بدلًا من إسقاطها بصمت، لذا يخبرك البناء بما لم يستطع حمله.

أين لا توجد التأثيرات#

لا يوجد SpriteEffect(id, "glow", ...) يأخذ اسم التأثير كنص. كل تأثير أمره الخاص به بحيث يمكن تسمية معاملاته وتوثيقها وإكمالها أثناء الكتابة. لأي شيء يتجاوز هذه الأربعة، يتولى أمران آخران المهمة: SpriteBlendModes(id, src, dst) للمزج الإضافي أو المضاعف (نار، ليزر)، وSpriteShader(id, shaderID) لوضع شادر مُصرَّف بـLoadSpriteShader على sprite. كان كلاهما مُعلَنًا في الكتالوج لفترة طويلة بلا تنفيذ، وكان استدعاؤهما يرفع خطأ؛ يعملان الآن.