איך יצרתי משחק פאזל CSS טהור PlatoBlockchain Data Intelligence. חיפוש אנכי. איי.

איך יצרתי משחק פאזל CSS טהור

לאחרונה גיליתי את השמחה שביצירת משחקים ל-CSS בלבד. זה תמיד מרתק איך HTML ו-CSS מסוגלים להתמודד עם ההיגיון של משחק מקוון שלם, אז הייתי חייב לנסות את זה! משחקים כאלה מסתמכים בדרך כלל על ה-Checkbox Hack הישן שבו אנו משלבים את המצב המסומן/לא מסומן של קלט HTML עם :checked מחלקה פסאודו ב-CSS. אנחנו יכולים לעשות הרבה קסמים עם השילוב האחד הזה!

למעשה, אתגרתי את עצמי לבנות משחק שלם ללא צ'קבוקס. לא הייתי בטוח אם זה יהיה אפשרי, אבל זה בהחלט כן, ואני הולך להראות לך איך.

בנוסף למשחק הפאזל שנלמד במאמר זה, הכנתי אוסף של משחקי CSS טהורים, רובם ללא ה-Checkbox Hack. (גם הם זמינים על CodePen.)

רוצה לשחק לפני שנתחיל?

אני אישית מעדיף לשחק את המשחק במצב מסך מלא, אבל אתה יכול לשחק אותו למטה או לפתוח את זה כאן.

מגניב נכון? אני יודע, זה לא משחק הפאזל הטוב ביותר שראיתם אי פעם, אבל הוא גם לא רע בכלל עבור משהו שמשתמש רק ב-CSS ובכמה שורות HTML. אתה יכול בקלות להתאים את גודל הרשת, לשנות את מספר התאים כדי לשלוט ברמת הקושי, ולהשתמש באיזו תמונה שתרצה!

אנחנו הולכים ליצור מחדש את ההדגמה הזו ביחד, ואז לשים בו קצת ניצוץ נוסף בסוף בשביל כמה בעיטות.

פונקציונליות הגרירה והשחרור

בעוד שמבנה הפאזל די פשוט עם CSS Grid, היכולת לגרור ולשחרר חלקי פאזל היא קצת יותר מסובכת. נאלצתי להסתמך על שילוב של מעברים, אפקטי ריחוף ובוררי אחים כדי לעשות זאת.

אם תרחף מעל התיבה הריקה בהדגמה זו, התמונה זזה בתוכה ונשארת שם גם אם תזיז את הסמן מהקופסה. החוכמה היא להוסיף משך מעבר ועיכוב גדולים - כל כך גדולים שלוקח לתמונה הרבה זמן לחזור למיקומה ההתחלתי.

img {
  transform: translate(200%);
  transition: 999s 999s; /* very slow move on mouseout */
}
.box:hover img {
  transform: translate(0);
  transition: 0s; /* instant move on hover */
}

ציון רק את transition-delay זה מספיק, אבל שימוש בערכים גדולים הן על ההשהיה והן על משך הזמן מקטין את הסיכוי ששחקן אי פעם יראה את התמונה זזה אחורה. אם אתה מחכה 999s + 999s - שזה בערך 30 דקות - ואז תראה את התמונה זזה. אבל אתה לא, נכון? זאת אומרת, אף אחד לא ייקח כל כך הרבה זמן בין התור, אלא אם כן יתרחק מהמשחק. אז אני רואה בזה טריק טוב למעבר בין שתי מדינות.

שמתם לב שגם ריחוף על התמונה מפעיל את השינויים? זה בגלל שהתמונה היא חלק מאלמנט הקופסה, וזה לא טוב עבורנו. נוכל לתקן זאת על ידי הוספה pointer-events: none לתמונה אבל לא נוכל לגרור אותה מאוחר יותר.

זה אומר שעלינו להציג אלמנט נוסף בתוך .box:

התוספת הזו div (אנחנו משתמשים בכיתה של .a) ייקח את אותו אזור כמו התמונה (הודות ל-CSS Grid ו grid-area: 1 / 1) ויהיה האלמנט שמפעיל את אפקט הרחף. וכאן נכנס לתמונה בורר האחים:

.a {
  grid-area: 1 / 1;
}
img {
  grid-area: 1 / 1;
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

מרחפת על ה .a אלמנט מזיז את התמונה, ומכיוון שהוא תופס את כל המקום בתוך הקופסה, זה כאילו אנחנו מרחפים מעל הקופסה במקום זאת! ריחוף עם התמונה כבר לא מהווה בעיה!

בוא נגרור ונשחרר את התמונה שלנו בתוך התיבה ונראה את התוצאה:

ראית את זה? אתה קודם כל תופס את התמונה ומעביר אותה לקופסה, שום דבר מפואר. אבל ברגע שאתה משחרר את התמונה אתה מפעיל את אפקט הריחוף שמניע את התמונה, ואז אנו מדמים תכונת גרירה ושחרור. אם אתה משחרר את העכבר מחוץ לקופסה, שום דבר לא קורה.

הממ, הסימולציה שלך לא מושלמת כי אנחנו יכולים גם לרחף על התיבה ולקבל את אותו אפקט.

נכון ונתקן זאת. עלינו להשבית את אפקט הריחוף ולאפשר אותו רק אם נשחרר את התמונה בתוך הקופסה. נשחק עם הממד שלנו .a מרכיב כדי לגרום לזה לקרות.

עכשיו, ריחוף על התיבה לא עושה כלום. אבל אם תתחיל לגרור את התמונה, ה .a אלמנט מופיע, ולאחר שחרורו בתוך הקופסה, נוכל להפעיל את אפקט הריחוף ולהזיז את התמונה.

בואו ננתח את הקוד:

.a {
  width: 0%;
  transition: 0s .2s; /* add a small delay to make sure we catch the hover effect */
}
.box:active .a { /* on :active increase the width */
  width: 100%;
  transition: 0s; /* instant change */
}
img {
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

לחיצה על התמונה מפעילה את :active פסאודו מחלקה שעושה את .a אלמנט ברוחב מלא (הוא שווה בתחילה ל 0). המצב הפעיל יישאר פעיל עד שנשחרר את התמונה. אם נשחרר את התמונה בתוך הקופסה, ה .a אלמנט חוזר ל width: 0, אבל אנחנו נפעיל את אפקט הרחף לפני שהוא יקרה והתמונה תיפול בתוך הקופסה! אם אתה משחרר אותו מחוץ לקופסה, שום דבר לא קורה.

יש מוזרות קטנה: לחיצה על התיבה הריקה גם מזיזה את התמונה ושוברת את התכונה שלנו. כַּיוֹם, :active מקושר ל .box אלמנט, אז לחיצה עליו או על כל אחד מילדיו תפעיל אותו; ועל ידי כך, אנו בסופו של דבר מציגים את .a אלמנט והפעלת אפקט הריחוף.

אנחנו יכולים לתקן את זה על ידי משחק pointer-events. זה מאפשר לנו להשבית כל אינטראקציה עם .box תוך שמירה על האינטראקציות עם מרכיבי הילד.

.box {
  pointer-events: none;
}
.box * {
  pointer-events: initial;
}

עַכשָׁיו תכונת הגרירה והשחרור שלנו מושלמת. אלא אם כן אתה יכול למצוא איך לפרוץ אותה, הדרך היחידה להזיז את התמונה היא לגרור אותה ולשחרר אותה בתוך התיבה.

בניית רשת הפאזל

חיבור הפאזל הולך להרגיש קליל בהשוואה למה שעשינו זה עתה עבור תכונת הגרירה והשחרור. אנחנו הולכים להסתמך על רשת CSS וטריקים ברקע כדי ליצור את הפאזל.

הנה הרשת שלנו, כתובה בפאג מטעמי נוחות:

- let n = 4; /* number of columns/rows */
- let image = "https://picsum.photos/id/1015/800/800";

g(style=`--i:url(${image})`)
  - for(let i = 0; i < n*n; i++)
    z
      a
      b(draggable="true") 

הקוד אולי נראה מוזר אבל הוא מתחבר ל-HTML פשוט:

<g style="--i: url(https://picsum.photos/id/1015/800/800)">
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
  <!-- etc. -->
</g>

אני בטוח שאתה תוהה מה נסגר עם התגים האלה. לאף אחד מהאלמנטים הללו אין משמעות מיוחדת - אני רק מוצא שהקוד הרבה יותר קל לכתוב באמצעותו <z> מאשר חבורה של <div class="z"> או מה שלא יהיה.

כך מיפיתי אותם:

  • <g> הוא מיכל הרשת שלנו שמכיל N*N <z> אלמנטים.
  • <z> מייצג את פריטי הרשת שלנו. זה משחק את התפקיד של .box אלמנט שראינו בסעיף הקודם.
  • <a> מפעיל את אפקט הריחוף.
  • <b> מייצג חלק מהתמונה שלנו. אנו מיישמים את draggable תכונה עליו מכיוון שלא ניתן לגרור אותו כברירת מחדל.

בסדר, בוא נרשום את מיכל הרשת שלנו <g>. זה ב-Sass במקום ב-CSS:

$n : 4; /* number of columns/rows */

g {
  --s: 300px; /* size of the puzzle */

  display: grid;
  max-width: var(--s);
  border: 1px solid;
  margin: auto;
  grid-template-columns: repeat($n, 1fr);
}

אנחנו בעצם הולכים להפוך לילדי הרשת שלנו - ה <z> אלמנטים - גם רשתות ויש להם את שניהם <a> ו <b> בתוך אותו אזור רשת:

z {
  aspect-ratio: 1;
  display: grid;
  outline: 1px dashed;
}
a {
  grid-area: 1/1;
}
b {
  grid-area: 1/1;
}

כפי שאתה יכול לראות, שום דבר מפואר - יצרנו רשת בגודל מסוים. שאר ה-CSS שאנו צריכים הוא עבור תכונת הגרירה והשחרור, אשר מחייבת אותנו למקם באופן אקראי את החלקים סביב הלוח. אני הולך לפנות לסאס בשביל זה, שוב בשביל הנוחות של היכולת לעבור ולעצב את כל חלקי הפאזל עם פונקציה:

b {
  background: var(--i) 0/var(--s) var(--s);
}

@for $i from 1 to ($n * $n + 1) {
  $r: (random(180));
  $x: (($i - 1)%$n);
  $y: floor(($i - 0.001) / $n);
  z:nth-of-type(#{$i}) b{
    background-position: ($x / ($n - 1)) * 100% ($y / ($n - 1)) * 100%;
    transform: 
      translate((($n - 1) / 2 - $x) * 100%, (($n - 1)/2 - $y) * 100%) 
      rotate($r * 1deg) 
      translate((random(100)*1% + ($n - 1) * 100%)) 
      rotate((random(20) - 10 - $r) * 1deg)
   }
}

אולי שמתם לב שאני משתמש ב-Sass random() פוּנקצִיָה. כך אנו מקבלים את המיקומים האקראיים עבור חלקי הפאזל. זכור שנעשה זאת להשבית עמדה זו כאשר מרחפת מעל <a> אלמנט לאחר גרירה ושחרור התואם שלו <b> אלמנט בתוך תא הרשת.

z a:hover ~ b {
  transform: translate(0);
  transition: 0s;
}

באותה לולאה, אני גם מגדיר את תצורת הרקע עבור כל חלק בפאזל. כולם יחלקו באופן הגיוני את אותה תמונה כמו הרקע, וגודלה צריך להיות שווה לגודל הרשת כולה (מוגדר עם --s מִשְׁתַנֶה). משתמש באותו background-image וקצת מתמטיקה, אנו מעדכנים את background-position כדי להציג רק חלק מהתמונה.

זהו זה! משחק הפאזל CSS בלבד שלנו נעשה טכנית!

אבל אנחנו תמיד יכולים להשתפר, נכון? הראיתי לך איך ליצור רשת של צורות של חלקי פאזל במאמר אחר. בואו ניקח את אותו רעיון וניישם אותו כאן, נכון?

צורות של חלקי פאזל

הנה משחק הפאזל החדש שלנו. אותה פונקציונליות אבל עם צורות מציאותיות יותר!

זוהי המחשה של הצורות על הרשת:

איך יצרתי משחק פאזל CSS טהור

אם תסתכל מקרוב תבחין שיש לנו תשע צורות שונות של חלקי פאזל: ה ארבע פינות, ה ארבעה קצוות, ו אחד לכל השאר.

הרשת של חלקי הפאזל שהכנתי במאמר השני אליו התייחסתי הוא קצת יותר פשוט:

אנחנו יכולים להשתמש באותה טכניקה המשלבת מסכות CSS והדרגות כדי ליצור את הצורות השונות. למקרה שאתה לא מכיר mask ושיפועים, אני ממליץ בחום לבדוק המקרה הפשוט הזה כדי להבין טוב יותר את הטכניקה לפני המעבר לחלק הבא.

ראשית, עלינו להשתמש בבוררים ספציפיים כדי למקד לכל קבוצת אלמנטים שחולקים את אותה צורה. יש לנו תשע קבוצות, אז נשתמש בשמונה בוררים, בתוספת בורר ברירת מחדל שבוחר את כולם.

z  /* 0 */

z:first-child  /* 1 */

z:nth-child(-n + 4):not(:first-child) /* 2 */

z:nth-child(5) /* 3 */

z:nth-child(5n + 1):not(:first-child):not(:nth-last-child(5)) /* 4 */

z:nth-last-child(5)  /* 5 */

z:nth-child(5n):not(:nth-child(5)):not(:last-child) /* 6 */

z:last-child /* 7 */

z:nth-last-child(-n + 4):not(:last-child) /* 8 */

הנה איור שמראה איך זה ממפה לרשת שלנו:

איך יצרתי משחק פאזל CSS טהור PlatoBlockchain Data Intelligence. חיפוש אנכי. איי.
איך יצרתי משחק פאזל CSS טהור

עכשיו בואו נתמודד עם הצורות. בואו נתמקד בלימוד רק אחת או שתיים מהצורות כי כולן משתמשות באותה טכניקה - וכך, יש לכם שיעורי בית להמשיך ללמוד!

עבור חלקי הפאזל במרכז הרשת, 0:

mask: 
  radial-gradient(var(--r) at calc(50% - var(--r) / 2) 0, #0000 98%, #000) var(--r)  
    0 / 100% var(--r) no-repeat,
  radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% - var(--r) / 2), #0000 98%, #000) 
    var(--r) 50% / 100% calc(100% - 2 * var(--r)) no-repeat,
  radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
  radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);

הקוד עשוי להיראות מורכב, אבל בואו נתמקד בשיפוע אחד בכל פעם כדי לראות מה קורה:

שני שיפועים יוצרים שני עיגולים (מסומנים בירוק וסגול בהדגמה), ושני שיפועים אחרים יוצרים את החריצים שאליהם מתחברים חלקים אחרים (המסומן בכחול ממלא את רוב הצורה ואילו זה המסומן באדום ממלא את החלק העליון). משתנה CSS, --r, קובע את הרדיוס של הצורות המעגליות.

איך יצרתי משחק פאזל CSS טהור PlatoBlockchain Data Intelligence. חיפוש אנכי. איי.
איך יצרתי משחק פאזל CSS טהור

צורת חלקי הפאזל במרכז (מסומנים 0 באיור) הוא הקשה ביותר לביצוע מכיוון שהוא משתמש בארבעה שיפועים ויש לו ארבע עקומות. כל החלקים האחרים מלהטטים עם פחות שיפועים.

לדוגמה, חלקי הפאזל לאורך הקצה העליון של הפאזל (מסומנים 2 באיור) משתמש בשלושה מעברים במקום ארבעה:

mask: 
  radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% + var(--r) / 2), #0000 98%, #000) var(--r) calc(-1 * var(--r)) no-repeat,
  radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
  radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);

הסרנו את השיפוע הראשון (העליון) והתאמנו את ערכי השיפוע השני כך שיכסה את החלל שנותר מאחור. לא תבחין בהבדל גדול בקוד אם תשווה בין שתי הדוגמאות. יש לציין שאנו יכולים למצוא תצורות רקע שונות כדי ליצור את אותה צורה. אם תתחיל לשחק עם שיפועים אתה בטוח תמצא משהו שונה ממה שאני עשיתי. אתה יכול אפילו לכתוב משהו יותר תמציתי - אם כן, שתף אותו בתגובות!

בנוסף ליצירת הצורות, תגלו גם שאני מגדיל את הרוחב ו/או הגובה של האלמנטים כמו להלן:

height: calc(100% + var(--r));
width: calc(100% + var(--r));

חלקי הפאזל צריכים להציף את תא הרשת שלהם כדי להתחבר.

איך יצרתי משחק פאזל CSS טהור PlatoBlockchain Data Intelligence. חיפוש אנכי. איי.
איך יצרתי משחק פאזל CSS טהור

הדגמה סופית

הנה שוב ההדגמה המלאה. אם תשווה את זה לגרסה הראשונה תראה את אותו מבנה קוד ליצירת הרשת ואת תכונת הגרירה והשחרור, בתוספת הקוד ליצירת הצורות.

שיפורים אפשריים

המאמר מסתיים כאן אבל נוכל להמשיך ולשפר את הפאזל שלנו עם עוד יותר תכונות! מה דעתך על טיימר? או אולי איזה מזל טוב כשהשחקן מסיים את הפאזל?

אני עשוי לשקול את כל התכונות הללו בגרסה עתידית, אז שימו עין על ריפו של GitHub שלי.

גלישה את

ו CSS היא לא שפת תכנות, הם אומרים. הא!

אני לא מנסה להצית קצת #HotDrama על ידי זה. אני אומר את זה כי עשינו כמה דברים לוגיים מאוד מסובכים וכיסינו הרבה מאפיינים וטכניקות של CSS לאורך הדרך. שיחקנו עם CSS Grid, מעברים, מיסוך, מעברי צבע, בוררים ומאפייני רקע. שלא לדבר על כמה טריקים של Sass שבהם השתמשנו כדי להפוך את הקוד שלנו לקל להתאמה.

המטרה לא הייתה לבנות את המשחק, אלא לחקור CSS ולגלות מאפיינים וטריקים חדשים שתוכלו להשתמש בהם בפרויקטים אחרים. יצירת משחק מקוון ב-CSS הוא אתגר שדוחף אותך לחקור את תכונות ה-CSS בפירוט רב וללמוד כיצד להשתמש בהן. בנוסף, זה פשוט כיף שאנחנו מקבלים משהו לשחק איתו כשהכל נאמר ונעשה.

אם CSS היא שפת תכנות או לא, זה לא משנה את העובדה שאנחנו תמיד לומדים על ידי בנייה ויצירת דברים חדשניים.

בול זמן:

עוד מ טריקים של CSS