4 min. read

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

TypeScript

TypeScript היא שפה מבוססת על JavaScript אשר מתקמפלת אליה.
היא מוסיפה תכונות נוספות ל-JS וספריות נוספות שאין ב-JS.
כגון:

  • סוגי משתנים סטטיים
  • ממשקים
  • מרחב שמות - NameSpaces
  • מודולים

TypeSciprt היא JavaScript וההפך

  • כל קובץ סקריפט של JS יכול להתקמפל עם קבצי TS.

  • ניתן לשנות קבצי .js ל-.ts והם ייתקמפלו.

Typescript Hello World

app.ts:

1
2
let message: string = 'Hello, World!';
console.log(message);

TS מאפשרת לנו ליצור משתנים אשר הסוג שלהם ידוע מראש.
התחביר : <type> נותן לנו אפשרות לומר איזה סוג משתנה זה.

בעזרת tsc ניתן לקמפל קבצי Typescript.

אם מריצים קומפילציה של הקוד:

1
tsc app.ts

אז מתקבל הקוד JS הבא.
app.js:

1
2
var message = 'Hello, World!';
console.log(message);

ובסוף ניתן להריץ את הקוד הזה בדפדפן או ב-node:

1
node app.js

תמיכה ב-TypeScript

TS נתמכת בכל סביבה ש-JS רצה בה והיא לא תלויית דפדפן או סביבה.
ניתן להריץ אותה על דפדפנים, שרתים כגון node-js או כל מנוע שתומך ב-JS.

קיימת גם תמיכה בסביבות עבודה כגון Visual Studio Code אשר אני אישית משתמש בו הרבה.

בעיתיות ב-TypeScript

לא כל טכנולוגיה היא מושלמת וצריך לדעת את החסרונות אל מול היתרונות.

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

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

  • אינטגרציה מורכבת יותר.
    יש אלגנטיות מסוימת ב-Javascript vanila מכיוון שאין צורך בכלים חיצוניים כדי לבנות ולהריץ קוד.
    בתוכניות מורכבות יותר צריך להשתמש גם ב:

    • שרתים - תוכנה שתריץ את הקוד שלנו במקרה ואנחנו בצד שרת.
    • Bundler - משהו שמאגד את הקוד שלנו בצורה פשוטה על מנת שנוכל לשלוח אותו.
    • Minifier - כדי לדחוס וליצור קוד שאינו קריא יש להשתמש בכלי שמצמצם ומשנה את הקוד.

Phaser 3

https://phaser.io/phaser3

ספרייה הבנויה על JS ו-TS לפיתוח משחקים בדפדפן.

לספרייה יכולות רבות וזו לא רק ספרייה לציור גראפי, ניתן לעשות בספרייה:

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

ועוד….

התקנה והרצה

ההרצה של משחק Phaser3 יכולה להיות מורכבת כי היא כוללת גם הרצה של שרת ווב.
מכיוון שצריך runtime אשר יריץ לנו את הקובץ ויביא קבצים אחרים, תמונות או משאבים.
כמו כן צריך Bundler שייבנה לנו את הקוד וייאגד אותו כדי שנוכל לשים אותו ב-production.

אני בחרתי ב-Parcel בתור ה-Bundler .

Parcel

בכללי הוא כלי לבנייה של קוד עבור הווב.
נשתמש עבורו בשביל:

  • לקמפל את קוד ה-TS.
  • לאגד את הקבצים שלנו לתיקייה תוצאות.
  • לעלות שרת ווב כדי להריץ את המשחק שלנו.

האתר: https://parceljs.org/

תבנית למשחק עם Parcel ו-Phaser3

בשביל לכתוב רק ב-JS יש את התבנית הזו:
https://github.com/ourcade/phaser3-parcel-template

בשביל לבנות בעזרת-TS ניתן להשתמש בתנית הזו:
https://github.com/ourcade/phaser3-typescript-parcel-template

התבנית של הקבצים נראית ככה:

1
2
3
4
5
6
7
8
9
10
.
├── dist
├── node_modules
├── public
├── src
│ ├── scenes
│ │ ├── HelloWorldScene.ts
│ ├── index.html
│ ├── main.ts
├── package.json
  • package.json - קונפיגורציה למודול של המשחק.
  • src - כל הקוד שלנו מוכל שם וגם קבצי ה-HTML.
  • public - קבצי משאבים כגון תמונות, סאונד או מודלים
  • dist - תיקייה הפלט של Parcel.

תריצו את הפקודה ותראו שזה עובד:

1
parcel src/index -p 8000

בעיות שהיו לי עם Parcel

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"engines": {
"node": "12.22.4"
},
"browserslist": [
"> 1%",
"not dead"
],
"parcelCleanPaths": [
"dist"
],
"staticFiles": {
"staticPath": "public",
"watcherGlob": "**"
}
}

החוסר שלהם גרמו לי לחלק מבעיות ביצירה של קבצי הפלט.

ללמוד מדוגמאות

דוגמאות

הרצה של משחק

index.html:

1
2
3
4
5
6
7
8
<html>
<head>
<title>MyGame</title>
</head>
<body>
<script src="main.ts" type="module"></script>
</body>
</html>

main.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Phaser from 'phaser'

import PracticeScene from './scenes/PracticeScene'
import MainMenuScene from './scenes/MainMenuScene'
import SelectLevelScene from './scenes/SelectLevelScene'

const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 1200,
height: 600,
backgroundColor: 0xfffff,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 200 },
debug: true
}
},
scene: [MainMenuScene, PracticeScene, SelectLevelScene]
}

export default new Phaser.Game(config)

המערכת מציירת על המסך בעזרת אחד משני מנועים - Canvas או WebGL.
ה-AUTO בוחר את היכולת של WebGL ואם הדפדפן לא תומך בו אז הוא ייבחר ב-Canvas.

טעינה והצגה של תמונה

לכל סצינה יש כמה מתודות חשובות בסדר החיים שלה.

  • init - יצירה של אובייקטים
    כאן ניצור מחלקות או קונפיגורציות לפני הרצה וטעינה.
  • preload - טעינה של משאבים לזיכרון בסצינה
    כאן נטען תמונות, סאונד או כל משאב שאנחנו צריכים.
  • create - יצירת סצינה
    כאן נוסיף לסצינה אובייקטים של Phaser.
  • update - פונקצית עדכון עבור המצב של הסצינה

למשל סצינה שטוענת תמונה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class MainScene extends Phaser.Scene {
private mCharacter: Phaser.GameObjects.Image;

constructor() {
super('MainScene')
}

preload() {
this.load.image('character', 'images/ninja.png');
}

public create() {
this.mCharacter = this.add.image(100, 100, 'character');
}

public update() {
}
}

ה- super('MainScene') נותן מפתח לסצינה כדי שמערכת הסצינות תדע באיזה סצינה מדובר.

ה-preload טוען את התמונה בעזרת this.load.image.
נותנים לפונקציית image פרמטר ראשון שהוא המפתח של התמונה, ופרמטר שני את הנתיב לתמונה מתיקייה ה-Public.

ב-create אנחנו מוסיפים את התמונה לאובייקט this.mCharacter במיקום (100,100).

תוצאה:

שחקן קופץ

MainScene.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export class MainScene extends Phaser.Scene {
private mCharacter;

constructor() {
super('MainMenu')
}

preload() {
this.load.image('character', 'images/ninja.png');
}

public create() {
this.mCharacter = this.physics.add.sprite(100, 100, 'character');
this.mCharacter.body.velocity.set(200, 200);
this.mCharacter.body.collideWorldBounds = true;
this.mCharacter.body.bounce.set(0.5);
}

public update() {
}
}

בעזרת this.physics.add.sprite נוסיף sprite שניתן לערוך אותו כדי שמערכת הפיזיקה של פייסר תעבוד.
ה-velocity מודד פיקסל בשנייה - ז”א 200 פיקסלים בשנייה לשני הכיוונים.
collideWorldBounds גורם לו לקפוץ אם הוא נתקע בגבולות החלון.
bounce.set(1.2) גורם לו לקפוץ בהתאם לערך.
ערך של - 1 ייגרום למהירות לא להשתנות.
ערך קטן מ-1 ייגרום למהירות לקטון עם כל קפיצה וערך גדול יותר מ-1 ייגרום לו להאיץ.

תוצאה:


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

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

התנהגות רבה מוסתרת מאיתנו ובצדק - כדי שנוכל להתמקד בלוגיקה של המשחק ולא לוגיקה של התשתית.

תודה על הקריאה!


אהבתם? מוזמנים להביע תמיכה כאן: כוס קפה