Hiệu ứng sprite và chữ
Bóng đổ, viền, glow và blur trên sprite 2D và trên chữ, kèm chi phí của từng loại, tác động của nó lên một sprite đang di chuyển, và công thức làm tay mà nó thay thế.
Cái này dùng để làm gì#
Cho đến nay một sprite 2D chỉ có thể mang một màu nhuộm và một ảnh, không gì khác. Để tách một nhân vật khỏi nền, bạn phải tự dựng hiệu ứng bằng tay: tạo một sprite thứ hai, nhuộm nó thành màu đen, dịch nó ra, đặt nó ra sau, và giữ nó đồng bộ với sprite đầu tiên ở từng khung hình. Nó có hiệu quả. Nhưng nó cũng là bốn dòng sổ sách phụ trên mỗi nhân vật, và ngày bạn quên một trong số đó, cái bóng sẽ trễ nhịp so với chủ của nó.
Bốn lệnh làm việc đó thay bạn, trên sprite lẫn trên chữ:
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
Không còn gì khác cần bảo trì. Di chuyển sprite, phóng to nó, lật nó, chạy hoạt ảnh của nó: các hiệu ứng đi theo.
Bốn hiệu ứng#
Shadow#
SpriteShadow(spriteID, offsetX, offsetY, softness, opacity)
SpriteShadowColor(spriteID, red, green, blue)
offsetX và offsetY tính theo đơn vị màn hình của riêng bạn, đếm giống hệt
SpritePosition: Y dương là hướng xuống. softness làm bóng lan rộng ra;
opacity chạy từ 0 đến 255. Màu mặc định là đen trừ khi bạn nói khác.
SpriteShadow(hero, 3, 5, 4, 110)
SpriteShadowColor(hero, 20, 0, 40) ' a cold shadow, not a grey one
Một bóng với opacity 0 là không có bóng. Đó là cách bạn tắt nó.
Outline#
SpriteOutline(spriteID, thickness, red, green, blue)
Đường viền đi theo hình dạng silhouette của sprite, không phải theo hình chữ nhật nó nằm trong đó: một PNG trong suốt sẽ có viền quanh phần hình vẽ, không phải quanh cả file. Độ dày tính theo đơn vị màn hình, và 1 đến 3 là khoảng hữu ích. Vượt qua mức đó, tám hướng mà đường viền được dựng từ đó sẽ bắt đầu lộ ra thành tám thuỳ.
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)
Một quầng sáng có màu, lan ra size đơn vị vượt khỏi silhouette. Nó nằm sau
sprite và trước bóng, đó là điều cho phép bạn dùng cả hai cùng lúc.
SpriteGlow(pickup, 255, 210, 0, 10) ' this one is worth walking to
Blur#
SpriteBlur(spriteID, radius)
Blur là trường hợp khác biệt. Ba hiệu ứng kia được vẽ xung quanh sprite; blur thay thế chính ảnh của sprite bằng một bản sao đã làm mờ của nó. Điều đó có hai ý nghĩa đáng biết:
- nó không tốn gì thêm ở mỗi khung hình (ảnh mờ được tính toán một lần), và
- vệt mờ bị cắt tại mép của ảnh. Một hình vẽ chạm đến mép file PNG của nó sẽ mất đi phần quầng sáng lẽ ra sẽ tràn ra ngoài. Hãy để lại vài pixel trong suốt ở lề trong file nếu bạn định làm mờ nó.
SpriteColor vẫn hoạt động trên một sprite đã bị làm mờ.
Tắt tất cả, rồi bật lại#
SpriteEffects(spriteID, 0) ' all four off
SpriteEffects(spriteID, 1) ' all four back, exactly as they were
SpriteEffects không xoá các cài đặt của bạn. Nó là một công tắc, không phải
một lệnh reset, nên một nhân vật có thể nhấp nháy mà không bắt bạn phải khai
báo lại cái bóng của nó sau đó.
Bốn hiệu ứng tương tự trên chữ#
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)
Chữ ký các lệnh khớp y hệt với các lệnh trên sprite, nên không có gì mới cần học. Hai khác biệt về hành vi:
- Một glow thắng độ mềm (softness) của một shadow. Cả hai được vẽ bằng cùng một cơ chế, và một glow không có softness thì vô hình trong khi một shadow không có softness vẫn là một cái bóng. Khi bạn yêu cầu cả hai, glow sẽ nhận lấy softness đó.
- Các lệnh này áp dụng cho chữ vector, không áp dụng cho chữ bitmap-font.
Chữ được vẽ bằng
TextFontImagelà một tổ hợp các sprite; các lệnh này sẽ bị bỏ qua âm thầm trên nó. Hãy dùng các hiệu ứng sprite trên các sprite trong trường hợp đó.
Một ví dụ thực tế: đọc được trên mọi nền#
Vấn đề mà các lệnh này thật sự giải quyết là độ tương phản mà bạn không kiểm soát được. Một điểm số màu trắng vô hình trên nền tuyết; một điểm số màu đen vô hình vào ban đêm. Một đường viền giải quyết chuyện đó một lần, cho mọi nền bạn sẽ từng vẽ.
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
Đọc lại một hiệu ứng#
Mọi tham số bạn ghi đều có một hàm đọc, theo cách SpriteColor có
SpriteColorRed. Đó là quy tắc của dialect này, và các lệnh này tuân theo nó.
IF SpriteEffectsEnabled(hero) = 1
SpriteEffects(hero, 0)
ENDIF
alpha = SpriteShadowAlpha(hero) ' 0 to 255
size = SpriteGlowSize(hero) ' 0 means no glow
Bộ đầy đủ, theo từng nhóm: SpriteShadowOffsetX, SpriteShadowOffsetY,
SpriteShadowBlur, SpriteShadowAlpha, SpriteShadowColorRed,
SpriteShadowColorGreen, SpriteShadowColorBlue, SpriteGlowRed,
SpriteGlowGreen, SpriteGlowBlue, SpriteGlowSize,
SpriteOutlineThickness, SpriteOutlineRed, SpriteOutlineGreen,
SpriteOutlineBlue, SpriteBlurRadius, SpriteEffectsEnabled, cộng thêm
cùng mười bảy lệnh đó với tiền tố Text.
Hai quy tắc đáng biết:
- Một sprite chưa từng được gán hiệu ứng nào tự báo cáo là ĐANG BẬT. Đó là trạng thái mặc định; trả lời 0 sẽ khiến một chương trình nghĩ rằng hiệu ứng của nó đã bị cắt trước khi nó kịp đặt bất kỳ hiệu ứng nào.
- Hỏi về một sprite không tồn tại sẽ trả lời 0, nó không làm dừng chương
trình. Đó là cách dialect này xử lý các câu hỏi ở khắp mọi nơi:
SpriteHittrả lời 0 khi không có gì chạm vào, và các chương trình đọc số 0 đó mà không cần kiểm tra nó trước.
Chi phí của nó#
Shadow, outline và glow đều được vẽ ra, nên chúng tốn draw call: một cho shadow (ba khi nó mềm), bốn cho một glow, tám cho một outline. Một màn hình với hai trăm sprite có viền là một màn hình đang vẽ một nghìn tám trăm quad, và điều đó sẽ lộ rõ. Chúng dành cho số ít thứ mà người chơi thật sự cần nhìn vào: nhân vật, vật phẩm nhặt được, đơn vị đang được chọn.
Blur không tốn gì thêm ở mỗi khung hình.
Một quy tắc thực tế: đặt hiệu ứng lên những gì di chuyển và quan trọng, không đặt gì lên nền.
Những gì đi theo khi xuất ra một game#
| Target | Shadow, outline, glow | Blur | Hiệu ứng chữ |
|---|---|---|---|
| Editor | có | có | có |
| Xuất HTML5 | có | có | có |
| Native và wasm (Bevy) | có | không | không |
Shadow, outline và glow được dựng từ những sprite bình thường, nên chúng đi theo bất cứ nơi nào sprite đi theo. Blur và các hiệu ứng chữ cần một nhịp cầu mà các target Bevy chưa có: báo cáo chuyển đổi khi xuất sẽ nêu tên chúng thay vì âm thầm bỏ rơi, nên một bản build sẽ cho bạn biết nó không mang theo được gì.
Nơi các hiệu ứng không tồn tại#
Không có SpriteEffect(id, "glow", ...) nhận tên hiệu ứng dưới dạng chuỗi.
Mỗi hiệu ứng là một lệnh riêng để các tham số của nó có thể được đặt tên, viết
tài liệu và tự hoàn thành khi bạn gõ. Cho bất cứ điều gì vượt ra ngoài bốn
hiệu ứng này, hai lệnh khác đảm nhận: SpriteBlendModes(id, src, dst) cho
kiểu hoà trộn cộng dồn hay nhân (một đám lửa, một tia laser), và
SpriteShader(id, shaderID) để đặt một shader được biên dịch bởi
LoadSpriteShader lên một sprite. Cả hai từng được khai báo trong catalogue
suốt một thời gian dài mà không có phần triển khai, và gọi chúng từng gây ra
lỗi; giờ chúng đã hoạt động.