کلام آخر

حالا که مفاهیم اساسی جاوااسکریپت رو یاد گرفتید، دنیایی از فرصت‌ها مقابل شماست. زمینه‌های متعددی هستن که شما می‌تونید توشون از دانشی که الان به دست آوردید استفاده کنید. این که قدم بعدی شما چی باشه، بستگی داره به این که دوست داشته باشید چه مهارت‌های دیگه‌ای یاد بگیرید و بخواید وارد چه حوزه‌ای بشید.

اگه قصدتون این باشه که برنامه‌های تحت وب طراحی کنید، باید HTML و CSS و React یاد بگیرید تا بتونید برای پروژه‌هاتون رابط کاربری مناسب طراحی کنید. اگه قصد یادگیری برنامه‌نویسی سمت سرور داشته باشید، باید Node.js یاد بگیرید. می‌تونید Electron یاد بگیرید و برنامه‌های دسکتاپ بنویسید. Electron به شما اجازه می‌ده تا با فناوری‌های مورد استفاده در وب، برنامه‌های دسکتاپ بنویسید. اگه بخواید، می‌تونید به صورت تخصصی و حرفه‌ای کار داده‌نمایی و مصورسازی اطلاعات انجام بدید، در این صورت باید یه نگاهی به D3 بندازید. می‌تونید با یادگیری React Native برنامه‌های موبایل بنویسید. می‌تونید با یادگیری Phaser بازی بسازید. می‌تونید با یادگیری Three.js کار انیمیشن و جلوه‌های سه‌بعدی انجام بدید؛ مقدمه همه این‌ها یادگیری جاوااسکریپته.

انیمیشن

برای این که بتونید توی برنامه‌تون انیمیشن درست کنید، p5.js به صورت پیشفرض کدی که داخل draw نوشته باشیدو شصت بار در هر ثانیه اجرا می‌کنه؛ به هر بار اجرا شدن این کد، اصطلاحاً می‌گن یک «فرِیم».

فرِیم رِیت

یه اصطلاحی هست به نام «فرِیم رِیت» که ممکنه قبلاً توی نرم‌افزارای تولید انیمیشن باهاش برخورد کرده باشید یا توی بعضی از بازی‌ها دیده‌باشید. فرِیم رِیت یعنی در یک بازه زمانی مشخص، مثلاً یه ثانیه، چند فریم روی صفحه نمایش به تصویر درمیاد. برنامه‌ای که توی هر ثانیه شصت فریم نمایش بده، یعنی داره با فریم رِیتِ شصت فریم بر ثانیه اجرا می‌شه. توی برنامه‌های p5.js برای این که فریم رِیتو چک کنیم، می‌تونیم از تابع frameRate استفاده کنیم.

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     console.log(frameRate())
}

اگه دوست نداشتید برنامه‌تون با این فریم رِیت اجرا بشه با همین تابع frameRate می‌تونید مشخص کنید که برنامه با چه فریم رِیتی اجرا بشه؛ فقط کافیه فریم رِیت مورد نظرتون رو در قالب یک پارامتر توی تابع frameRate مشخص کنید؛ دقت کنید که p5.js سعی می‌کنه فرِیم رِیت برنامه رو به اون حدی که شما مشخص کردید برسونه، اما در واقعیت فریم رِیتِ برنامه ممکنه نسبت به ویژگی‌های دستگاهی که برنامه داره توش اجرا می‌شه کاهش پیدا کنه.

function setup() {
     createCanvas(windowWidth, windowHeight);

     // specify frame rate
     frameRate(30)

}
function draw() {
     console.log(frameRate())
}

سرعت و جهت انیمیشن

برای این که بتونیم یه انیمیشنِ روان درست کنیم، متغیرهایی می‌سازیم تا بتونیم یه سری اعداد توش ذخیره کنیم و بعد با اجرای هر فریم، تغییرشون می‌دیم. مثال بعدی یه شکل رو از سمت چپ صفحه نمایش سمت به راست حرکت می‌ده:

let radius = 45
let x = -radius
let theSpeed = 5

function setup() {
     createCanvas(windowWidth, windowHeight);
     ellipseMode(RADIUS)
}
function draw() {
     background(0)

     // change the position based on speed
     x += theSpeed

     // draw the shape
     arc(x, windowHeight/2, radius, radius, 0.5, 5.5)
  
}

وقتی کد بالا رو اجرا کنید می‌بینید که شکل، بعد از مدتی از سمت راست تصویر خارج می‌شه و ما دیگه نمی‌بینیمش؛ یه شرط به کد بالا اضافه می‌کنم تا وقتی شکل از سمت راستِ تصویر خارج شد، برگرده به جای اولش و دوباره از سمت چپ شروع به حرکت کنه:

let radius = 45
let x = -radius
let theSpeed = 5

function setup() {
     createCanvas(windowWidth, windowHeight);
     ellipseMode(RADIUS)
}
function draw() {
     background(0)

     // change the position based on speed
     x += theSpeed

     // if the shape is off screen
     if(x > windowWidth + radius) {
     x = -radius
     }

     // draw the shape
     arc(x, windowHeight/2, radius, radius, 0.5, 5.5)

}

کد بالا توی هر فریم، شرطی که گذاشتیم رو چک می‌کنه: اگه مقدار x از عرض صفحه به‌علاوه شعاع دایره بیشتر شده بود، دوباره شکل رو برمی‌گردونه به جای اولش.

حرکت بین دو نقطه

بعضی وقتا نیاز داریم یه شکل بین دو نقطه جابجا بشه. با چند خط کُد، می‌شه نقطه شروع و پایانو مشخص کرد و بعد مختصات مسیرو محاسبه کرد. کد پایینو اجرا کنید و مقادیر متغیرها رو تغییر بدید و خودتون ببینید چطوری کار می‌کنه:

const startX = 50
const startY = 50
const stopX = 500
const stopY = 500
let x = startX
let y = startY

// speed
const step = 0.005

// percentage traveled
let pct = 0

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {
     background(0)
     if(pct < 1) {
          x = startX + ((stopX - startX) * pct)
          y = startY + ((stopY - startY) * pct)
          pct += step
     }
     ellipse(x, y, 15, 15)
}

انیمیشن رندوم

بر خلاف حرکت خطی‌ای که توی اسکیس‌های قبل دیدید، توی دنیای واقعی معمولاً سرعت‌ها متغیر و بعضاً تصادفی هستن. مثل جابجا شدن یه برگ توی باد یا حرکت مورچه روی زمین؛ می‌شه تلاش کرد و این طور کیفیت‌های پیش‌بینی‌ناپذیرِ دنیای واقعی رو با به وجود آوردن اعداد تصادفی شبیه‌سازی کرد. قبلاً درباره روش استفاده کردن از اعداد تصادفی نوشته بودم. تابع random توی p5.js این طور مقادیرو محاسبه می‌کنه؛ می‌تونیم دو تا عدد به عنوان پارامترهای این تابع استفاده کنیم و برای تولید یه عدد تصادفی از یه بازه‌ی عددی استفاده کنیم. توی اسکیس بعدی، جای دایره‌هایی که داره کشیده می‌شه توسط مقادیر تصادفی مشخص می‌شه. دقت کنید که این بار از تابع background داخل draw استفاده نمی‌کنیم تا بتونیم همه دایره‌هایی که کشیده می‌شن رو ببینیم:

const theSpeed = 10
const diameter = 100
let x
let y

function setup() {
     createCanvas(windowWidth, windowHeight);
     x = windowWidth/2
     y = windowHeight/2
     background(205)
}
function draw() {
     x += random(-theSpeed, theSpeed)
     y += random(-theSpeed, theSpeed)
     ellipse(x, y, diameter, diameter)
}
با استفاده از اعداد تصادفی، می توان انیمیشن های مختلفی ایجاد کرد

استفاده از مثلثات برای ساختن انیمیشن

امیدوارم مثلثاتِ محدودی که توی این دوره استفاده می‌کنیم شما رو کنجکاو کنه درباره مثلثات مطالعه کنید. ما توی این دوره خیلی عمیق وارد ریاضیات و مثلثات نمی‌شیم، اما چند تا از کاربردای مثلثات توی انیمیشنو با هم‌دیگه می‌بینیم.

تابع‌های sin و cos در p5.js مقادیری بین 1 و -1 به ما برمی‌گردونن که همون سینوس و کسینوس زاویه‌ایه که مشخص می‌کنیم؛ زاویه‌ها باید با واحد رادیان مشخص بشن. معمولاً اعدادی که توابع sin و cos به ما می‌دن باید در یه عددی ضرب بشن که بشه خوب ازشون استفاده کرد:

let angle = 0
const scalar = 100
const theSpeed = 0.1

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {

     // adjust the angle
     angle += theSpeed

     // set y position
     const y1 = sin(angle + 0.25) * scalar
     const y2 = sin(angle + 0.50) * scalar
     const y3 = sin(angle + 0.75) * scalar

     // create 3 circles
     ellipse(width/2 - 50, height/2 + y1, 45, 45)
     ellipse(width/2 + 00, height/2 + y2, 45, 45)
     ellipse(width/2 + 50, height/2 + y3, 45, 45)

}
یک فریم از انیمیشن سه دایره

وقتی sin و cos با هم استفاده بشن، می‌تونن حرکت دورانی به وجود بیارن. توی مثال بعدی، مقادیر تابع cos مختصات x رو تعیین می‌کنن و مقادیر تابع sin مشخص‌کننده مختصات y هستن:

let angle = 0
const scalar = 100
const theSpeed = 0.1

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {

     // adjust the angle
     angle += theSpeed

     // positions
     const x = cos(angle) * scalar
     const y = sin(angle) * scalar

     // create the circles
     ellipse(width/2 + x, height/2 + y, 50, 50)

}
یک فریم از انیمیشن یک دایره

اگه توی کد قبلی یه تغیر کوچیک ایجاد کنیم و مقدار scalar رو توی هر فریم یه خُرده زیاد کنیم، می‌تونیم به جای دایره، یه مارپیچ درست کنیم:

let angle = 0
let scalar = 100
const theSpeed = 0.1

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {

     // adjust the angle and scalar
     angle += theSpeed
     scalar += theSpeed * 3

     // positions
     const x = cos(angle) * scalar
     const y = sin(angle) * scalar

     // create the circles
     ellipse(width/2 + x, height/2 + y, 50, 50)

}

می‌بینید یه تغییر کوچیک چقدر می‌تونه توی تصویر خروجی تأثیر داشته باشه؟ حالا وقتشه از چیزایی که یاد گرفتید استفاده کنید، یه خُرده خلاقیت به خرج بدید و هر جوری که دوست دارید با کد بازی کنید. خروجی‌های مختلف رو تست کنید، من یه تغییراتی روی کد انجام دادم و خروجی کارم این طوری شد:

اصلاً انیمیشن نمی‌خوام

اگه برنامه شما نیاز به انیمیشن نداشت، می‌تونید از تابع noLoop داخل setup استفاده کنید، این طوری هر چیزی که داخل draw باشه هم فقط یک بار فراخوانی می‌شه.

متغیرهای جاوااسکریپت

متغیر چیه؟

توی جاوااسکریپت، برای ذخیره کردن اطلاعات، از متغیرهای جاوااسکریپت استفاده می‌کنیم. چه زمانی نیاز هست که اطلاعات رو ذخیره کنیم؟ هر وقت نیاز داشته باشیم تا یه «مقدار» یا value رو چند جای مختلف استفاده کنیم. کار کردن با متغیرها باعث می‌شه سایرین راحت‌تر کد شما رو متوجه بشن و در اصطلاح، کد شما «خواناتر» می‌شه.

چطوری یه متغیر تعریف کنیم؟

اگه بخوایم یه متغیر تعریف کنیم، باید از یکی از کلیدواژه‌های let یا const استفاده کنیم و بعد برای اون متغیر یه اسم انتخاب کنیم؛ از کلیدواژه const وقتی استفاده می‌کنیم که نیاز به یه مقدار ثابت داشته‌باشیم و از let زمانی استفاده می‌کنیم که مقدارمون متغیر باشه؛ توی این دوره نمی‌خوام خیلی عمیق وارد تفاوت‌های const و let بشم ولی درباره انتخاب کردن اسم متغیرها یه سری قواعد هست که باید همین جا یاد بگیرید:

  • اسمی که انتخاب می‌کنید باید معنی‌دار باشه
  • اسمی که انتخاب می‌کنید نباید با عدد شروع بشه
  • بین کلماتی که برای اسم متغیر انتخاب می‌کنید نباید فاصله باشه
  • برای انتخاب کردن اسم‌ها، توی جاوااسکریپت از یه جور استاندارد املایی استفاده می‌شه که بهش می‌گن نگارشِ شُتُری یا camelCase؛ این طوریه که کلمه اول با حرف کوچیک شروع می‌شه و از کلمه دوم به بعد، کلمات با حرف بزرگ شروع می‌شن و به همدیگه می‌چسبن؛ توی مثال بعدی می‌بینید که متغیرهای windowWidth و windowHeight با رعایت کردن همین استاندارد نوشته شدن (رعایت این استاندارد اجباری نیست اما شدیداً توصیه می‌شه از این روش برای نوشتن اسامی استفاده بشه)

استفاده از متغیرهای جاوااسکریپت توی p5.js

حالا بیاید یه متغیر تعریف کنیم و ببینیم در عمل، تعریف کردن یه متغیر چطوری انجام می‌شه. همون کدی که توی درس قبلی نوشتیمو یه بار دیگه استفاده می‌کنم و این دفعه برای مشخص کردن اندازه‌های دایره از یه متغیر استفاده می‌کنم:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     if(mouseIsPressed) {
          fill(0)
     } else {
          fill(255)
     }

     // this is a variable, you can change circle size here
     const circleSize = 80

     ellipse(mouseX, mouseY, circleSize, circleSize)
}

بعد از این که فایل index.html رو توی مرورگر باز کردید، عدد متغیر circleSize رو تغییر بدید، فایل رو ذخیره کنید و صفحه مرورگرو رفرش کنید. می‌بینید که هم عرض و هم ارتفاع دایره فقط با تغییر دادن یه متغیر، عوض می‌شه.

متغیر ازپیش‌تعریف‌شده

توی setup موقع اجرا کردن تابع createCanvas از دو تا متغیر از پیش‌تعریف‌شده‌ی windowWidth و windowHeight استفاده کردیم که به ترتیب عرض و ارتفاع صفحه مرورگرو بهمون می‌ده؛ این دو تا متغیر رو ما تعریف نکردیم و از متغیرهای پیش‌فرض p5.js هستن. پس p5.js شامل تعدادی متغیر ازپیش‌تعریف‌شده‌س که کاربردهای مختلفی دارن. حالا یه اسکیس دیگه انجام بدیم. این دفعه می‌خوام یه دایره وسط تصویر بکشم و وسطش یه مستطیل درست کنم:

function setup() {

     // general
     createCanvas(windowWidth, windowHeight)
     background(1, 186, 240)
     noStroke()

}
function draw() {

     // circle
     const circleSize = 200
     fill(237, 35, 93)
     ellipse(windowWidth / 2, windowHeight / 2, circleSize, circleSize)

     // rectangle
     const rectWidth = 150
     const rectHeight = 30
     fill(255)
     rect(windowWidth / 2 - rectWidth / 2, windowHeight / 2 - rectHeight / 2, rectWidth, rectHeight)

}

این مثال یه خرده نیاز به توضیح داره. من کد رو به سه قسمت تقسیم کردم:

بخش اول که با کامنت general مشخص شده تنظیمات عمومی صفحه‌س؛ این جا با تابع background مشخص کردم که رنگ پس‌زمینه چی باشه و همچنین با استفاده از تابع noStroke مشخص کردم که اَشکالی که قراره بعد از این به نمایش دربیاد دورخط یا استروک نداشته باشه.

بخش دوم که با کامنت circle مشخص شده شامل یه متغیره که اندازه دایره توش تعریف شده. بعد هم از تابع fill استفاده کردم که به دایره رنگ بدم. بعد خود دایره کشیده شده. برای این که دایره دقیقاً وسط صفحه قرار بگیره عرض و ارتفاع صفحه رو تقسیم بر دو کردم.

بخش سوم که با کامنت rectangle مشخص شده هم شامل دو تا متغیره که عرض و ارتفاع مستطیل رو مشخص می‌کنه؛ این جا هم از fill استفاده کردم برای مشخص کردن رنگ مستطیل. اگه دقت کنید مجبور شدم عرض و ارتفاع مستطیل رو تقسیم بر دو کنم تا مستطیل وسط دایره قرار بگیره.

با استفاده از متغیرهای جاوااسکریپت یه علامت راهنمایی و رانندگی ساختیم!

توی این مثال دیدید که من متغیرها رو توی تابع draw ساختم و توی همون تابع هم استفاده کردم. ممکنه این سؤال توی ذهنتون به وجود بیاد که اگه این متغیرها توی تابع setup ساخته می‌شدن چی می‌شد؟ جواب اینه که برنامه با خطا مواجه می‌شد و درست اجرا نمی‌شد؛ این به خاطر اینه که توی جاوااسکریپت، اگه یه متغیر داخل یه تابع ساخته بشه، فقط داخل همون تابع می‌شه ازش استفاده کرد. پس برنامه پایین با خطا مواجه می‌شه:

function setup() {

     // general
     createCanvas(windowWidth, windowHeight)
     background(1, 186, 240)
     noStroke()

     // variables
     const circleSize = 200
     const rectWidth = 150
     const rectHeight = 30

}
function draw() {

     // circle
     fill(237, 35, 93)
     ellipse(windowWidth/2, windowHeight/2, circleSize, circleSize)

     // rectangle
     fill(255)
     rect(windowWidth/2 - rectWidth/2, windowHeight/2 - rectHeight/2, rectWidth, rectHeight)

}

اما اگه نیاز باشه، می‌تونیم متغیرها رو بیرون تابع draw و قبل از تابع setup بسازیم و توی هر تابعی که خواستیم ازشون استفاده کنیم. این طوری برنامه به مشکلی نمی‌خوره؛ به این متغیرها می‌گن متغیرهای عمومی؛ یعنی منحصر به هیچ تابعی نیستن و هر جایی قابل استفاده هستن.

// variables
const circleSize = 200
const rectWidth = 150
const rectHeight = 30

function setup() {
     createCanvas(windowWidth, windowHeight)
     background(1, 186, 240)
     noStroke()
}
function draw() {

     // circle
     fill(237, 35, 93)
     ellipse(windowWidth/2, windowHeight/2, circleSize, circleSize)

     // rectangle
     fill(255)
     rect(windowWidth/2 - rectWidth/2, windowHeight/2 - rectHeight/2, rectWidth, rectHeight)

}

چه چیزهایی رو می‌شه توی متغیرهای جاوااسکریپت ذخیره کرد؟ «انواعِ داده»

توی مثال‌هایی که تا الان دیدید، فقط اعداد رو داخل متغیرها ذخیره کردیم. در واقع تنها «نوع داده‌»ای که تا الان داخل متغیرهای جاوااسکریپت ذخیره کردیم، اعداد بودن. جاوااسکریپت هشت نوع داده یا دیتا تایپ داره که می‌شه اون‌ها رو توی متغیرها ذخیره و بعد ازشون استفاده کرد؛ اون هشت نوع داده اینا هستن:

  • عدد ـ Number ـ ممکنه عدد صحیح یا اعشاری باشه
  • استرینگ ـ String ـ همون متنه و بین کوتیشن‌مارک یا بک‌تیک قرار می‌گیره
  • بولیَن ـ Boolean ـ یعنی true یا false (صحیح یا غلط)
  • نال ـ Null ـ مقدارِ خالی
  • تعریف‌نشده ـ Undefined ـ متغیر تعریف شده ولی هیچ مقداری نداره
  • آرایه ـ Array ـ فهرستی از داده‌ها
  • تابع ـ Function
  • آبجکت ـ Object

شش تای اول از این هشت تا رو خیلی خلاصه الان توضیح می‌دم، دو تای دیگه که پیچیده‌تر هستن رو توی یادداشت‌های بعدی کامل یاد می‌گیریم.

اول: عدد

اعداد می‌تونن عدد صحیح یا اعشاری باشن. می‌تونیم توی فرمول‌ها از اعداد استفاده کنیم و باهاشون اعمال ریاضی، مثل جمع و تفریق انجام بدیم.

const firstNumber = 5
const secondNumber = 10

console.log(firstNumber + secondNumber)
// 15

این البته خیلی مثال ساده‌ایه. توی جاوااسکریپت یه آبجکتی داریم که بهش می‌گیم Math که به معنی ریاضیه و بهمون امکانات زیادی می‌ده که بتونیم با اعداد کارای مختلفی انجام بدیم. توی مثال پایین ببینید که با مِتُدهای مربوط به آبجکت Math چطوری اعداد رو به سمت بالا یا پایین رُند می‌کنیم و یا از بین مجموعه اعداد، عدد کمینه یا بیشنه رو مشخص می‌کنیم:

console.log(PI) 
// 3.141592653589793

console.log(Math.round(PI)) 
// 3, to round values to the nearest number

console.log(Math.round(9.81)) 
// 10

console.log(Math.floor(PI)) 
// 3, rounding down

console.log(Math.ceil(PI)) 
// 4, rounding up

console.log(Math.min(-5, 3, 20, 4, 5, 10)) 
// -5, returns the minimum value

console.log(Math.max(-5, 3, 20, 4, 5, 10)) 
// 20, returns the maximum value

به اون عبارتی که بعد از آبجکت Math نوشتیم می‌گن مِتُدِ مربوط به اون آبجکت. مِتُدهای مربوط به آبجکت Math خیلی زیادن و برای هر عملیات ریاضی که از ذهنتون بگذره، جاوااسکریپت یه مِتُدِ آماده داره؛ من نمی‌خوام همه این مِتُدها رو بررسی کنم یا توضیح بدم چون خیلی زیاد هستن و واقعاً بیشترشون تا حالا به کارم نیومده، فقط یادتون باشه که برای هر جور عملیات ریاضی که فک کنید یه مِتُدی وجود داره و این متدها محدود به استفاده در p5.js نیستن، بلکه توی هر محیطی که بشه توش جاوااسکریپت نوشت قابل استفاده هستن.

دوم: استرینگ

استرینگ همون متنه و بین کوتیشن‌مارک یا علامت نقل‌قول نوشته می‌شه. فرض کنید می‌خوایم استرینگ‌های مختلف رو به همدیگه متصل کنیم و به صورت یک متن واحد نمایش بدیم؛ برای این کار از علامت + استفاده می‌کنیم و متغیرها رو بین متن قرار می‌دیم:

const space = ' '
const firstName = 'Ali'
const country = 'Iran'
const city = 'Tehran'
const language = 'JavaScript'
const age = 33
const personInfo = 'Hi!' + space + 'My name is' + space + firstName

console.log(personInfo)
// Hi! My name is Ali

البته برای این کار یه روش دیگه هم وجود داره که به نظر من روش بهتریه که بهش می‌گن «درج در استرینگ» یا به انگلیسی String Interpolation؛ توی این روش دیگه از علامتِ + استفاده نمی‌شه و به جاش برای نمایش متغیرها از علامت $ استفاده می‌شه و بعد اسم متغیر توی براکت قرار می‌گیره؛ ضمناً کل استرینگ بین دو تا علامت بک‌تیک قرار می‌گیره. علامت بک‌تیک این علامته: ` و توی صفحه کلید من سمت چپ عدد یک قرار داره. پس اگه بخوایم خلاصه کنیم، توی روش درج در استرینگ:

  • قبل از متغیرها از علامت دلار استفاده می‌شه
  • متغیرها داخل براکت قرار می‌گیرن
  • کل استرینگ داخل علامت بک‌تیک نوشته می‌شه
const firstName = 'Ali'
const country = 'Iran'
const city = 'Tehran'
const language = 'JavaScript'
const age = 33
const personInfo = `Hi! My name is ${firstName} and I live in ${city}`

console.log(personInfo)
// Hi! My name is Ali and I live in Tehran

سوم: بولیَن

بولیَن نوعی از داده‌س که می‌تونه true یا false باشه (توی یادداشت قبلی خیلی مختصر بهش اشاره کردم)؛ یعنی یا درسته یا غلطه؛ خیلی ساده‌س و جلوتر که درباره مقایسه و منطق شرطی یاد بگیرید متوجه می‌شید که چقدر کاربردیه. فعلاً فقط بدونید هر وقت عمل مقایسه انجام بدیم، در واقع داریم یه بولیَن می‌سازیم که ممکنه true یا false باشه. خودتون می‌تونید یه سری مقایسه انجام بدید و نتیجه رو توی کنسول نگاه کنید:

console.log(4 > 3) 
// true

console.log(3 > 4) 
// false

console.log(3 == 3) 
// true

توی مثالی که اول همین درس داشتیم از متغیر mouseIsPressed استفاده کردیم؛ این متغیر یه بولیَنه و از متغیرهای پیش‌فرض p5.js هم هست، وقتی دکمه ماوس فشار داده بشه true و در غیر این صورت false می‌شه.

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     if(mouseIsPressed) {
          fill(0)
     } else {
          fill(255)
     }

     // this is a variable, you can change circle size here
     const circleSize = 80

     ellipse(mouseX, mouseY, circleSize, circleSize)
}

چهارم: نال

نال یعنی تُهی، یعنی هیچ. متغیر نال متغیریه که خالیه و هیچ چیزی توش نیست؛ دقت کنید که این هم‌معنی با عدد صفر نیست، چون عدد صفر یه عدده ولی نال هیچ چی نیست؛ همچنین این یه متغیر تعریف‌نشده نیست.

const empty = null

console.log(empty) 
// null

پنجم: تعریف‌نشده

اگه یه متغیر بسازید ولی هیچ مقداری بهش نسبت ندید، مقدار اون متغیر undefined یا تعریف‌نشده می‌شه.

let firstName

console.log(firstName) 
// undefined

نکته خیلی مهم توی مثال بالا اینه که برای ساختن متغیر توی مثال بالا دیگه از const استفاده نکردم؛ چون توی جاوااسکریپت وقتی با کلیدواژه const یه متغیر می‌سازید باید حتماً مقدار اون متغیر رو هم تعریف کنید؛ در غیر این صورت با خطا مواجه می‌شید؛ این در حالیه که متغیرهایی که با let ساخته می‌شن، می‌تونن ابتدا مقداری نداشته باشن و مقدار اون‌ها بعداً مشخص بشه.

ششم: آرایه

آرایه‌ها در واقع فهرستی از داده‌ها هستن که با علامت , از همدیگه جدا شدن و می‌تونن شامل هر نوعِ داده‌ی دیگه‌ای باشن. اگه لازم باشه با مجموعه‌ای از داده‌ها کار کنیم، مثلاْ مجموعه‌ای از کلمات، باید از یه آرایه استفاده کنیم. با استفاده از علامت‌های [] که بهش می‌گیم براکت، می‌شه یه آرایه تعریف کرد و ازش استفاده کرد:

const words = ['this', 'is', 'a', 'simple', 'array']

console.log(words[0])
// this

console.log(words[3])
// simple

console.log(words.length)
// 5 

آرایه‌های جاوااسکریپت zero-base هستن؛ یعنی وقتی محتوای آرایه شماره‌گذاری می‌شن، شماره‌گذاری از صفر شروع می‌شه؛ برای همینه که توی مثال بالا وقتی خواستیم به اولین موردِ آرایه دسترسی پیدا کنیم از words[0] استفاده کردیم. همچنین از متُدِ length استفاده کردیم تا تعداد موارد داخل آرایه رو به دست بیاریم؛ متُدهای دیگه هم هستن که برای کار کردن با آرایه‌ها وجود داره. مثلاً متُد push یک آیتم یا مقدار به انتهای آرایه اضافه می‌کنه:

let words = ['this', 'is', 'a', 'simple', 'array']
words.push('apple')

console.log(words[5])
// apple

خب، این از شش تا انواعِ داده، می‌مونه «تابع» و «آبجکت» که بعداً قراره حسابی درباره‌شون یاد بگیریم.

چک کردن انواع داده

بعضی وقتا نیاز داریم ببینیم چه نوع داده‌ای توی یه متغیر ذخیره شده؛ برای این کار از مِتُدِ typeof استفاده می‌کنیم:

const firstName = 'Ali'
const age = 33
let job

console.log(typeof firstName) 
// string

console.log(typeof age)
// number

console.log(typeof job) 
// undefined, because a value was not assigned

یه خُرده ریاضی

خبر خوب اینه که پروژه‌های جاوااسکریپت معمولاً ریاضیات خیلی پیچیده‌ای ندارن و با اعمال خیلی ساده ریاضی سر و کار دارن (مگه این که پروژه خاصی باشه). خیلیا فک می‌کنن برای این که برنامه‌نویس خوبی بشن حتماً باید خیلی ریاضی بلد باشن. البته که دانش ریاضی خیلی وقتا توی کدنویسی به داد آدم می‌رسه، اما تجربه نشون داده که بیشتر وقتا اعمال خیلی ساده ریاضی هستن که توی پروژه‌ها کاربرد دارن. علامت‌های ساده‌ای مثل به‌علاوه، منها و ستاره وقتی بین اعداد (یا متغیرهای شامل اعداد) قرار بگیرن، اعمال ریاضی رو بین اون اعداد انجام می‌دن. با این علامت‌هایی که این زیر لیست کردم می‌تونیم روی اعداد، اعمال ساده ریاضی انجام بدیم. توی مثال بعدی می‌تونید اعداد رو به دلخواه خودتون تغییر بدید و نتیجه هر کدوم از اعمال ریاضی رو توی کنسول مرورگرتون ببینید.

  • جمع: +
  • تفریق: -
  • ضرب: *
  • تقسیم: /
  • باقی‌مانده: %
  • توان: **
const numOne = 5
const numTwo = 3
const sum = numOne + numTwo
const diff = numOne - numTwo
const mult = numOne * numTwo
const div = numOne / numTwo
const remainder = numOne % numTwo
const powerOf = numOne ** numTwo

console.log(sum)
console.log(diff)
console.log(mult)
console.log(div)
console.log(remainder)
console.log(powerOf)

جاوااسکریپت قوانینی داره که مشخص می‌کنه انجام کدوم اعمال ریاضی نسبت به سایر اعمال اولویت داره. یعنی کدوم محاسبات اول انجام می‌شه، کدوم محاسبات دوم، کدوم سوم و الی آخر؛ این اطلاعات به شما کمک می‌کنه بفهمید یه چنین کدی چطوری کار می‌کنه:

const x = 4 + 4 * 5
// 24

توی مثال بالا، اول 4 * 5 محاسبه می‌شه چون عمل ضرب بالاترین اولیت رو داره. بعد 4 به حاصل ضرب 4 * 5 اضافه می‌شه و نتیجه 24 به دست میاد. اگه دوست داشته باشید می‌تونید برنامه رو مجبور کنید که عمل جمع رو اول انجام بده؛ کافیه عمل جمعی که مد نظرتونه رو ببرید داخل یه جفت پرانتز. اعمال داخل پرانتز همیشه اولویت بالاتری نسبت به اعمال خارج از پرانتز دارن:

const x = (4 + 4) * 5
// 40

توی برنامه‌نویسی بعضی از محاسبات این قدر زیاد تکرار می‌شن که براشون یه میان‌برهایی طراحی شده. مثلاً اضافه کردن عدد 1 به یه متغیر با علامت ++ انجام می‌شه، یا کم کردن عدد 1 از یه متغیر با علامت -- به راحتی انجام می‌شه:

x++
y--

p5.js هم تابع‌های آماده‌ای داره که خیلی از عملیات‌های ریاضی رو برامون ساده‌تر می‌کنه. مثلاً رُند کردن اعداد با تابع round انجام می‌شه، با تابع random می‌شه یه عدد تصادفی توی بازه‌ای که مشخص می‌کنید، بسازید و با تابع cos می‌تونید کسینوس یه زاویه رو به دست بیارید.

const roundedValue = round(2.67)
// 3

const randomValue = random(-5, 5)
// a random number between -5 and 5

const cosineValue = cos(angle)
// calculates the cosine 

مقایسه در جاوااسکریپت

خیلی وقتا پیش میاد که لازمه مقادیری رو با همدیگه مقایسه کنیم، برای این کار باید از علامت‌های مربوط به مقایسه استفاده کنیم. می‌تونیم مقایسه کنیم که یه مقدار با یه مقدار دیگه برابره، یا مثلاً ازش کوچکتر یا بزرگتره.

  • فقط مقدار، برابر: ==
  • هم مقدار و هم نوع داده، برابر: ===
  • نابرابر: =!
  • بزرگتر از: >
  • کوچکتر از: <
  • بزرگتر یا مساوی: >=
  • کوچکتر یا مساوی: <=
console.log(3 > 2) // true
console.log(3 >= 2) // true
console.log(3 < 2) // false
console.log(2 < 3) // true
console.log(2 <= 3) // true
console.log(3 == 2) // false
console.log(3 != 2) // true

console.log(3 == '3') // true, compare only value
console.log(3 === '3') // false, compare both value and data type
console.log(3 !== '3') // true, compare both value and data type
console.log(3 != 3) // false, compare only value
console.log(3 !== 3) // false, compare both value and data type
  • علامت آمپرسَند ـ && ـ همه شروط درست باشن
  • علامت پایپ ـ || ـ یکی از شروط درست باشه
  • علامتِ نفی ـ ! ـ تبدیل درست به غلط و غلط به درست
// && ampersand
const check1 = 4 > 3 && 10 > 5 // true
const check2 = 4 > 3 && 10 < 5 // true
const check3 = 4 < 3 && 10 < 5 // false

// || pipe
const check4 = 4 > 3 || 10 > 5 // true
const check5 = 4 > 3 || 10 < 5 // true
const check6 = 4 < 3 || 10 < 5 // false

// ! negation
const check7 = 4 > 3 // true
const check8 = !(4 > 3) // false
const isLightOn = true
const isLightOff = !isLightOn // false
const isMarried = !false // true

شرط

خیلی وقتا نیاز داریم توی برنامه‌مون یه شرط بنویسیم؛ یعنی اگه یه چیزی true بود برنامه یه کاری برامون انجام بده و اگه false بود یه کار دیگه انجام بده؛ برای این کار می‌تونیم از کلیدواژه if استفاده کنیم و داخل پرانتز شرط رو بنویسیم، شرط می‌تونه شامل یه متغیر بولین باشه یا یه مقایسه (که باز هم در نهایت نتیجه‌ش یه بولیَنه)؛ بعد هم باید داخل {} بنویسیم که اگه شرط صادق بود چه اتفاقی قراره بیفته. اول همین صفحه، اولین مثالی که انجام دادیم شامل یه شرط بود. دقت کنید که چطور از if و else استفاده شده:

if(mouseIsPressed) {
     fill(0)
} else {
     fill(255)
}

فونت در p5.js

p5.js می‌تونه هر فونتی رو لود کنه و توی متن‌ها ازش استفاده کنه، حتی فونت‌های فارسی. می‌تونید از فونت‌هایی که از قبل روی سیستمتون نصب شده استفاده کنید، یا توی برنامه‌تون فونت‌های دیگه‌ای رو لود کنید. برای اعمال فونت روی متن‌ها، توی برنامه‌های p5.j می‌تونیم از تابع textFont استفاده کنیم و برای نوشتن خود متن‌ها، می‌شه از تابع text استفاده کرد. همچنین برای تغییر سایز متن از تابع textSize استفاده می‌شه:

const myString = 'یک قدم کوچک برای انسان'

function setup() {
     createCanvas(windowWidth, windowHeight);
     textFont('B Mitra')
}
function draw() {
     textSize(30)
     text(myString, 25, 60)
     textSize(22)
     text(myString, 25, 100)
}
یک نمونه از تایپوگرافی در p5.js

توی تابع text اولین پارامتر، همون متنیه که قراره نمایش بده و پارامترهای بعدی مختصات نمایش متن روی بومه. توی مثال بالا، فونتی که فراخونی کردید باید روی سیستمی که برنامه شما رو اجرا می‌کنه نصب باشه؛ اگه بخواید این طوری نباشه، یعنی نیازی نباشه که سیستم شما فونت رو داشته باشه، باید فونت همراه با برنامه شما لود بشه.

لود کردن فونت در p5.js

p5.js می‌تونه فونت‌هایی با پس‌وند ttf یا otf رو لود کنه؛ برای این کار باید این مراحل طی بشه:

  • فونت توی همون فولدر کنار فایل sketch.js قرار بگیره
  • CSS مرتبط با لود فونت توی فایل HTML قرار بگیره
  • فونت جدید به عنوان فونت برنامه معرفی بشه
  • حتماً از تابع background در draw استفاده بشه

من از فونت ایران‌سنس استفاده کردم و فایل فونت رو کنار فایل sketch.js قرار دادم؛ بعد باید این کُدِ CSS رو به فایل HTML اضافه کنیم؛ این کد شامل آدرس فایلِ فونته؛ همچنین یه اسم برای فونتی که داریم به برنامه معرفی می‌کنیم انتخاب می‌کنیم:

@font-face {
     font-family: 'iranSans';
     src: url(IRANSansX-Regular.ttf);
}

فایل index.html الان باید این شکلی شده باشه:

<!DOCTYPE html>
<html lang="">

<head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>p5.js example</title>
     <style>
          @font-face {
               font-family: 'iranSans';
               src: url(IRANSansX-Regular.ttf);
          }
          body {
               padding: 0;
               margin: 0;
          }
     </style>
     <script src="../p5.js"></script>
     <script src="sketch.js"></script>
</head>

<body>
     <main>
     </main>
</body>

</html>

و فایل sketch.js این شکلی:

const myString = 'یک قدم کوچک برای انسان'

function setup() {
     createCanvas(windowWidth, windowHeight);
     textFont('iranSans')
}
function draw() {
     background(255)
     textSize(30)
     text(myString, 25, 60)
     textSize(22)
     text(myString, 25, 100)
}

یک نمونه از تایپوگرافی در p5.js که فونت دلخواه به این برنامه اضافه شده است

دقیقاً مثل شکل‌ها، برای مشخص کردن رنگ متن‌ها هم می‌شه از تابع‌های fill و stroke استفاده کرد.

const myString = 'یک قدم کوچک برای انسان'

function setup() {
     createCanvas(windowWidth, windowHeight);
     textFont('iranSans')
}
function draw() {
     background(200)
     fill(255)
     stroke(225, 0, 0)
     strokeWeight(5)
     textSize(45)
     text(myString, 25, 100)
     strokeWeight(3)
     textSize(30)
     text(myString, 25, 160)
}
یک نمونه از تایپوگرافی در p5.js که ظاهر متن ها تغییر داده شده است

حالا که یاد گرفتیم چطوری می‌شه فونت فارسی به برنامه اضافه کرد و چطوری می‌شه متن تایپ کرد، می‌تونیم اسکیس بزنیم. می‌خوام یه حرف انتخاب کنم و با حرکت ماوس کوچیک و بزرگ و جابجاش کنم. دقت کنید این دفعه دیگه از تابع draw استفاده نمی‌کنم، بلکه از یه تابع دیگه استفاده می‌کنم به نام mouseMoved که همون طوری که از اسمش پیداس، بر اساس حرکت ماوس کُدی که داخلش نوشته شده رو اجرا می‌کنه (اگه با متغیر mouseX و mouseY آشنا نیستید باید «ورودی» رو بخونید):

const letter = 'ق'

function setup() {
     createCanvas(windowWidth, windowHeight);
     textFont('iranSans')
     textAlign(CENTER, CENTER)
     background(255)
}
function mouseMoved() {
     background(200)
     textSize((mouseX - width / 2) * 5 + 1)
     text(letter, width / 2, mouseY)
}

رنگ

توی دنیای دیجیتال، وقتی داریم درباره رنگ صحبت می‌کنیم باید دقیق باشیم. مثلاً این که بگیم اون دایره رو برام سرخابی کن فایده‌ای نداره. هر رنگی با استفاده از مجموعه‌ای از اعداد مشخص و متمایز می‌شه. مثلاً عدد 0 یعنی سیاه و 255 یعنی سفید. تا حالا رنگ‌های مختلفی توی اسکیس‌ها استفاده کردیم، الان می‌خوایم ببینیم چطور می‌تونیم رنگ‌ها رو کنترل کنیم و رنگی که نیاز داریمو بسازیم.

تابع رنگ

p5.js به صورت پیش‌فرض از سیستم RGB برای کار کردن با رنگ‌ها استفاده می‌کنه. RGB مخفف این کلماته:

  • R ـ Red ـ قرمز
  • G ـ Green ـ سبز
  • B ـ Blue ـ آبی

رنگ مورد نظرمونو باید از ترکیب این سه عدد به دست بیاریم؛ هر کدوم از این سه عدد می‌تونن مقداری بین 0 و 255 داشته باشن. مثلاً اگه برای تابع background پارامترهای 0, 0, 0 رو استفاده کنیم نتیجه اینه که سیاه به دست آوردیم و اگه 255, 255, 255 استفاده کنیم، پس‌زمینه‌مون سفید می‌شه. البته اگه مثل این دو تا سناریو قرار باشه هر سه تا عدد یکی باشن، فقط کافیه یکی از اعداد رو وارد کنید؛ یعنی به جای 255, 255, 255 فقط کافیه بنویسیم 255 و رنگ سفید رو بسازیم. دقت کنید وقتایی که فقط از یه پارامتر استفاده می‌کنید، یعنی هر سه تا مقدار RGB یک‌سان هستن، رنگ شما فقط می‌تونه سیاه یا سفید یا خاکستریِ تیره یا روشن باشه. اگه نیاز به غلظت‌های متنوع رنگی داشته باشید باید از هر سه پارامتر استفاده کنید.

255 بیشترین غلظتیه که یه رنگ می‌تونه داشته باشه. پس اگه پارامتر‌های 255, 0, 0 رو استفاده کنیم، یه رنگ کاملاً قرمز به دست میاریم، اگه از 0, 255, 0 استفاده کنیم، کاملاً سبز به دست میاریم و اگه از 0, 0, 255 استفاده کنیم، کاملاً آبی.

من معمولاً برای ساختن پالت مورد نیازم و همچنین به دست آوردن کد RGB از یه ابزار آنلاینی که توی این آدرس هست استفاده می‌کنم: https://coolors.co؛ پیشنهاد می‌کنم حتماً یه سری به این سایت بزنید و با امکاناتی که داره آشنا بشید.

یک پالت

پارامترِ چهارم

هر جایی که سه تا پارامتر RGB رو وارد می‌کنید، می‌تونید یه پارامتر چهارم هم وارد کنید؛ به این پارامتر چهارم می‌گن پارامترِ آلفا و برای همین بعضی جاها این استاندارد رو RGBA نام‌گذاری کردن. پارامترِ آلفا، میزان کدورت یا شفافیتِ رنگو مشخص می‌کنه و باز هم می‌تونه عددی بین 0 تا 255 باشه؛ در این صورت 0 یعنی کاملاً شفاف و 255 یعنی کاملاً کِدِر.

در نتیجه موقع انتخاب رنگ با مدل RGB، می‌تونیم یک مقدار، یا سه مقدار، یا چهار مقدار رو به عنوان پارامتر وارد کنیم و یه رنگ به دست بیاریم. نمی‌خوام با اطلاعات خیلی زیاد گیجتون کنم، ولی گوشه ذهنتون این رو هم نگه دارید که موقع استفاده کردن از این مدل می‌تونید فقط دو تا پارامتر هم وارد کنید؛ در این صورت پارامتر اول مشخص کننده‌ی رنگ‌های RGB خواهد بود و پارامتر دوم، مقدارِ آلفا.

خب حالا می‌دونید اعدادی که به عنوان پارامتر توی تابع‌هایی مثل background و fill و stroke استفاده کردیم معنی‌شون چیه. یادآوری می‌کنم که برای رنگ دادن به یه شکل از تابع fill و برای رنگ دادن به دورخطِ یه شکل از تابع stroke استفاده می‌کنیم. رنگ پیش‌فرض همه شکل‌ها توی p5.js سفید و دورخط همه شکل‌های به صورت پیش‌فرض سیاهه؛ یه تابع مفید دیگه هم که خوبه همین جا یاد بگیرید strokeWeight هست که اجازه می‌ده ضخامت دورخط رو مشخص کنید:

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {
     background(210)

     // circle 1
     fill(50, 50, 50)
     strokeWeight(3)
     stroke(75)
     ellipse(windowWidth/2, windowHeight/2, 300, 300)

     // circle 2
     fill(255, 50, 100)
     stroke(0)
     ellipse(windowWidth/2, windowHeight/2, 270, 270)

     // circle 3
     fill(1, 175, 240)
     ellipse(windowWidth/2, windowHeight/2, 240, 240)

}
چند دایره که روی هم قرار گرفته اند

توی مثال بالا از تابع fill قبل از کشیدن هر دایره استفاده کردم تا رنگ دایره‌ای که داره کشیده می‌شه رو مشخص کنم؛ دو تا تابع دیگه به نام‌های noFill و noStroke هستن که همون طوری که از اسمشون به پیداس وقتایی استفاده می‌شن که برای شکل به رنگ نیاز نداشته باشیم یا بخوایم شکل دورخط نداشته باشه.

مَپ کردن

توی درس قبلی دیدید که برنامه‌مون می‌تونه اطلاعاتی رو از موقعیتِ مکان‌نمای ماوس روی صفحه به دست بیاره. عددایی که توسط مکان‌نمای ماوس به دست میاد معمولاً باید یه تغییراتی روشون انجام بشه تا داخل برنامه‌ای که داریم می‌نویسیم قابل استفاده باشن. مثلاً فرض کنید عرض یه اسکیس 1920 پیکسل باشه و بخوایم از مکان‌نمای ماوس استفاده کنیم برای تغییر دادن رنگ پس‌زمینه؛ توی این درس هم یاد گرفتید که عددی که نیاز داریم باید بین 0 تا 225 قرار بگیره تا بتونیم رنگ‌ها رو باهاش کنترل کنیم؛ این جاس که باید از تابع map استفاده کنیم و به اصطلاح عددمونو مپ کنیم:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     const mapWidth = map(mouseX, 0, windowWidth, 0, 255)
     const mapHeight = map(mouseY, 0, windowHeight, 0, 255)
     background(mapWidth, mapHeight, 100)
}

توی این اسکیسِ خیلی ساده، از تابع background توی draw استفاده کردم. برای به دست آوردنِ غلظتِ رنگِ قرمز حرکت افقی ماوس رو مپ کردم، برای به دست آوردن غلظت رنگ سبز حرکت عمودی ماوس رو مپ کردم و غلظت رنگ آبی رو عدد ثابت صد قرار دادم.

تابعِ map پنج تا پارامتر داره:

  • اولین پارامتر ـ عددی که قراره مپ بشه
  • دومین پارامتر ـ پایین‌ترین عدد توی بازه فعلی
  • سومین پارامتر ـ بالاترین عدد توی بازه فعلی
  • چهارمین پارامتر ـ پایین‌ترین عدد توی بازه هدف
  • پنجمین پارامتر ـ بالاترین عدد توی بازه هدف
map(value, start1, stop1, start2, stop2)

// value (Number)
// the incoming value to be converted

// start1 (Number)
// lower bound of the value's current range

// stop1 (Number)
// upper bound of the value's current range

// start2 (Number)
// lower bound of the value's target range

// stop2 (Number)
// upper bound of the value's target range

ذخیره کردن رنگ

بعضی وقتا نیاز داریم یه رنگ رو توی یه متغیر ذخیره کنیم و بعداً ازش استفاده کنیم. توی اسکیس بعدی با استفاده از تابع frameRate یه برنامه‌ای می‌نویسم که محتویات draw رو توی هر ثانیه فقط یه بار اجرا کنه (بعداً درباره این بیشتر یاد می‌گیریم). بعد با استفاده از تابع color رنگ‌ها رو توی متغیرها ذخیره می‌‌کنم. در نهایت برنامه، توی هر ثانیه، سه تا رنگ تصادفی درست می‌کنه و روی صفحه نمایش بهم نشون می‌ده:

function setup(){
     createCanvas(windowWidth, windowHeight)
     frameRate(1)
}
  
function draw(){
     background(255)
     noStroke()

     const color1 = color(random(255), random(255), random(255))
     fill(color1)
     rect(0, 0, width/3, height)
    
     const color2 = color(random(255), random(255), random(255))
     fill(color2)
     rect(width * 1/3, 0, width/3, height)
    
     const color3 = color(random(255), random(255), random(255))
     fill(color3)
     rect(width * 2/3, 0, width/3, height)
                         
}
این برنامه هر ثانیه سه رنگ تصادفی درست می کند

معرفی 3 تا «ورودی» p5.js

ورودی چیه؟

ممکنه با خوندن تیتر این درس بپرسید «ورودی» چیه؟ توی اولین مطلب این دوره دیدید که توی p5.js برنامه ما می‌تونه از بولیَنِ mouseIsPressed استفاده کنه تا یک شرط تعریف بشه؛ این یکی از راه‌های ورود اطلاعات به برنامه‌مونه؛ برای این که اطلاعات وارد برنامه‌مون بشه راه‌های دیگه‌ای هم وجود داره که اصطلاحاً به هر کدوم از این راه‌های ورود اطلاعات، ورودی یا اینپوت می‌گیم؛ مثلا:

  • فشار دادن دکمه‌های ماوس
  • تغییر دادن موقعیت مکانی ماوس
  • فشار دادن دکمه‌های صفحه کلید
  • لمس کردن صفحه تاچ‌پد
  • استفاده کردن از میکروفون یا وب‌کم

ورودی: موقعیت مکانی ماوس

کدی که توی draw نوشته می‌شه دائماً در حال اجرا شدنه، برای همین می‌تونیم موقعیت مکانی ماوس روی صفحه رو تعقیب کنیم و عناصری رو روی صفحه به حرکت دربیاریم. متغیر mouseX و mouseY که توی مثال‌های قبلی هم ازشون استفاده کردیم، مختصات x و y نشان‌گر ماوس رو توی خودشون ذخیره می‌کنن. توی این مثال هر بار که کد توی draw اجرا می‌شه، یک دایره جدید کشیده می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight)
     fill(0, 50)
     noStroke()
}
function draw() {
     ellipse(mouseX, mouseY, 250, 250)
}
ورودی اطلاعات در این مثال، موقعیت مکانی ماوس است

توی مثال بعدی، باز هم هر بار که draw اجرا می‌شه، یه دایره روی صفحه کشیده می‌شه، اما این بار از تابع background استفاده کردم تا فقط آخرین دایره‌ای که کشیده شده دیده بشه. تابع background بوم رو با رنگی که به عنوان پارامتر دریافت می‌کنه، پُر می‌کنه، برای همین باید توی draw قرار بگیره تا قبل از کشیده شدن هر دایره، دایره‌ی قبل از بین بره:

function setup() {
     createCanvas(windowWidth, windowHeight)
     fill(0, 50)
     noStroke()
}
function draw() {
     background(204)
     ellipse(mouseX, mouseY, 250, 250)
}
یک دایره که با حرکت دادن ماوس حرکت می کند

متغیرهای pmouseX و pmouseY مختصات ماوس توی فریم قبلی رو ذخیره می‌کنن و مثل mouseX و mouseY هر بار که draw اجرا می‌شه آپدیت می‌شن؛ با استفاده از این متغیرها می‌شه خطوط پیوسته کشید:

function setup() {
     createCanvas(windowWidth, windowHeight)
     strokeWeight(10)
     stroke(0, 105)
}
function draw() {
     line(mouseX, mouseY, pmouseX, pmouseY)
}
کشیده شدن یک خط

کد بالا داره با استفاده از تابع line دائماً خط می‌کشه: پارامتر اول و دوم، مختصات x و y نقطه ابتدایی خط، و پارامتر سوم و چهارم، مختصات x و y نقطه انتهای خط رو تعریف می‌کنه. یه کار دیگه‌ای که با متغیرهای pmouseX و pmouseY می‌شه انجام داد اینه که سرعت حرکت ماوس رو به دست آورد؛ این طوری می‌شه بر اساس سرعت حرکت ماوس هم تغییراتی تو اَشکال به وجود آورد. سرعت حرکت ماوس رو می‌شه از فاصله بین جای فعلی ماوس و جای قبلی ماوس به دست آورد؛ یکی از توابع آماده p5.js به نام dist این محاسبات رو برای ما انجام می‌ده. توی مثال پایین، از سرعت ماوس برای تنظیم ضخامت خطی که داره کشیده می‌شه استفاده می‌کنیم:

function setup() {
     createCanvas(windowWidth, windowHeight)
     stroke(0, 105)
}
function draw() {
     const mouseSpeed = dist(mouseX, mouseY, pmouseX, pmouseY)
     strokeWeight(mouseSpeed)
     line(mouseX, mouseY, pmouseX, pmouseY)
}
در این مثال، ورودی اطلاعات، تغییر سرعت حرکت ماوس است

توی مثال بعدی می‌خوام یه برنامه بنویسم که یه طوری بفهمه مکان‌نمای ماوس سمت راست یا چپ یک خطه و اون خط رو به سمت مکان‌نمای ماوس حرکت بده؛ ببینید چطوری انجام می‌‎شه:

let x
let offset = 10

function setup() {
     createCanvas(windowWidth, windowHeight)
     x = windowWidth/2
}
function draw() {
     background(204)
     if(mouseX > x) {
          x += 0.5
          offset = -10 
     }
     if(mouseX < x) {
          x -= 0.5
          offset = 10
     }

  // line
  line(x, 0, x, windowHeight)

  // draw arrow left or right depending on offset value
  line(mouseX, mouseY, mouseX + offset, mouseY - 10)
  line(mouseX, mouseY, mouseX + offset, mouseY + 10)
  line(mouseX, mouseY, mouseX + offset*3, mouseY)

}
خط به سمت ماوس حرکت می کند

ممکنه بخوایم کدی بنویسیم که محدوده شکل دایره رو تشخیص بده و بفهمه که آیا مکان‌نمای ماوس داخل دایره قرار گرفته یا نه؛ برای این که این تست رو انجام بدیم دوباره از تابع dist استفاده می‌کنیم و فاصله مرکز دایره تا مکان‌نمای ماوس رو حساب می‌کنیم؛ بعد تست می‌کنیم که آیا این فاصله بیشتر از شعاع دایره‌مون هست یا نه. توی مثال بعدی، وقتی مکان‌نمای ماوس داخل دایره قرار بگیره، دایره شروع به بزرگ شدن می‌کنه. خودتون امتحان کنید:

let radius = 15

function setup() {
     createCanvas(windowWidth, windowHeight)
     ellipseMode(RADIUS)
     noStroke()
}
function draw() {
     background(204)
     let distance = dist(mouseX, mouseY, windowWidth/2, windowHeight/2)
     if(distance < radius) {
          radius++
          fill(0)
     } else {
          fill(255)
     }
     ellipse(windowWidth/2, windowHeight/2, radius, radius)
}

دقت کنید توی مثال بالا من از ellipseMode استفاده کردم و روی حالت RADIUS قرارش دادم؛ این طوری دو تا پارامتر آخر تابع ellipse مشخص‌کننده شعاع‌های عرضی و ارتفاعی دایره می‌شن. برای اطلاعات بیشتر درباره تابع ellipseMode می‌تونید این لینک رو ببینید.

ورودی: دکمه‌های صفحه کلید

مثل متغیر mouseIsPressed متغیر keyIsPressed هم زمانی که هر دکمه‌ای روی صفحه کلید فشار داده بشه true می‌شه و دقیقاً مثل mouseIsPressed می‌شه جاهای مختلف ازش استفاده کرد. متغیر key هم آخرین کلیدی که روی صفحه کلید فشار داده شده رو توی خودش ذخیره می‌کنه و تا وقتی یه کلید دیگه فشار داده بشه، اون رو توی خودش نگه می‌داره.

ورودی: لمس صفحه نمایش

برای دستگاه‌هایی که از صفحه نمایش لمسی پشتیبانی می‌کنن، p5.js می‌تونه تشخیص بده که صفحه نمایش لمس شده یا نه و این کارو از طریق متغیر بولیَنِ touchIsDown انجام می‌ده؛ برای این که بدونیم کجای صفحه لمس شده هم می‌شه از متغیرهای touchX و touchY استفاده کرد.

تابع جاوااسکریپت

تابع جاوااسکریپت چیه؟

می‌خوام با ساده‌ترین تعریفی که می‌تونم از تابع جاوااسکریپت ارائه بدم شروع کنم: یه تابع، مجموعه‌ای از کُدهاس که یک یا چند عملیات به‌خصوص انجام می‌ده. اصلاً نگران نباشید، قراره همه چی براتون کاملاً روشن بشه و بعد که یاد گرفتین چطوری از تابع‌ها استفاده کنید، قراره کلی کارهای باحال انجام بدید!

تابع‌ها اساسی‌ترین عناصر همه برنامه‌های p5.js هستن. وقتی یه تابع ساخته می‌شه، دیگه لازم نیست کدی که داخل اون تابع نوشته شده دوباره جای دیگه‌ای تکرار بشه، بلکه کافیه از همون تابع استفاده بشه. تا حالا توی خیلی از مثال‌ها از تابع‌های آماده‌ای مثل createCanvas و fill استفاده کردیم. علاوه بر تابع‌های آماده‌ای که p5.js در اختیارمون قرار می‌ده، خودمون هم می‌تونیم تابع بنویسیم و توی برنامه‌مون ازش استفاده کنیم.

قبل از هر چیز می‌خوام از تابع‌های آماده‌ی p5.js استفاده کنم و یه جغد بکشم:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(204)
     translate(110, 175)
     stroke(0)
     strokeWeight(70)
     line(0, -35, 0, -65)
     noStroke()
     fill(255, 255, 255)
     ellipse(-17.5, -65, 35, 35)
     ellipse(+17.5, -65, 35, 35)
     arc(0, -65, 70, 70, 0, PI)
     fill(0)
     ellipse(-14, -65, 8, 8)
     ellipse(+14, -65, 8, 8)
     quad(0, -58, 4, -51, 0, -44, -4, -51)
}
یک جغد که با تابع های آماده در p5.js کشیده شده است.

فعلاً خیلی با جزییات این کد کاری نداشته باشید و فقط این کد رو کپی کنید و نتیجه رو ببینید. دقت کنید که من از translate استفاده کردم؛ این تابع نقطه صفر و صفر بوم شما رو از بالا سمت چپ جابجا می‌کنه و به نقطه جدیدی که شما تعیین می‌کنید تغییر می‌ده؛ از این جا به بعد، همه مختصاتی که وارد می‌کنید نسبت به این نقطه صفر و صفرِ جدید تعیین می‌شه.

کدی که الان نوشتیم تا این جغد به وجود بیاد هیچ مشکلی نداره و خیلی هم منطقی به نظر می‌رسه؛ اما اگه قرار باشه به جای این یه دونه جغد، دو تا جغد خوشگل داشته باشیم، اون وقت اندازه کُدمون دو برابر می‌شه؛ ببینید:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(204)

     // owl one
     translate(110, 175)
     stroke(0)
     strokeWeight(70)
     line(0, -35, 0, -65)
     noStroke()
     fill(255, 255, 255)
     ellipse(-17.5, -65, 35, 35)
     ellipse(+17.5, -65, 35, 35)
     arc(0, -65, 70, 70, 0, PI)
     fill(0)
     ellipse(-14, -65, 8, 8)
     ellipse(+14, -65, 8, 8)
     quad(0, -58, 4, -51, 0, -44, -4, -51)

     // owl two
     translate(70, 0)
     stroke(0)
     strokeWeight(70)
     line(0, -35, 0, -65)
     noStroke()
     fill(255, 255, 255)
     ellipse(-17.5, -65, 35, 35)
     ellipse(+17.5, -65, 35, 35)
     arc(0, -65, 70, 70, 0, PI)
     fill(0)
     ellipse(-14, -65, 8, 8)
     ellipse(+14, -65, 8, 8)
     quad(0, -58, 4, -51, 0, -44, -4, -51)

}
دو جغد که با تابع های آماده در p5.js کشده شده اند.

به غیر از background که مربوط به رنگ پس‌زمینه بود، من همه خطوط رو کپی کردم و برای جغد جدید translate رو تغییر دادم تا کنار جغد قبلی قرار بگیره؛ درسته که داره کار می‌کنه، ولی این کد منطقی و بهینه نیست؛ چون تعداد زیادی خط تکراری توی کد وجود داره و این اصلاً جذاب نیست. از طرفی، فرض کنید به جای دو تا جغد قرار بود مثلاً پونزده تا جغد داشته باشیم؛ این جور مواقع باید چی کار کنیم؟

باید یه تابع بنویسیم!

توی مثال بعد یه تابع می‌نویسیم که برامون جغد بکشه. این طوری لازم نیست هر بار که به یه جغد نیاز داشتیم این همه خط کد بنویسیم یا کپی‌پیست کنیم:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(204)
     owl(110, 175)
     owl(70, 0)
     owl(70, 0)
}
function owl(x, y) {
     translate(x, y)
     stroke(0)
     strokeWeight(70)
     line(0, -35, 0, -65)
     noStroke()
     fill(255, 255, 255)
     ellipse(-17.5, -65, 35, 35)
     ellipse(+17.5, -65, 35, 35)
     arc(0, -65, 70, 70, 0, PI)
     fill(0)
     ellipse(-14, -65, 8, 8)
     ellipse(+14, -65, 8, 8)
     quad(0, -58, 4, -51, 0, -44, -4, -51)
}
این بار برای ساختن جغدها از یک تابع جاوااسکریپت استفاده کردیم.

با کلیدواژه function یه تابع ساختم و اسمش رو owl گذاشتم؛ توی پرانتزی که جلوش باز و بسته شده، دو تا پارامتر براش در نظر گرفتم که بتونم با هر بار فراخوندن تابع owl مختصات جغد جدیدو تغییر بدم، تا جایی که دوست دارم قرار بگیره. پارامترها بخش خیلی مهمی از تابع‌ها هستن، چون باعث می‌شه بتونیم از یه تابع ثابت، خروجی‌های متغیر بگیریم؛ مثلاً اگه می‌خواستیم می‌تونستیم رنگ و ابعاد جغدها رو هم با همین یک تابع تغییر بدیم؛ بعد از این که تابع رو درست کردم، اون رو سه بار توی تابع draw فراخوندم. می‌بینید؟ با این که این بار سه تا جغد داریم، اما کدمون کوتاه‌تر و تمیزتر از کد قبلی شده؛ این طوری بهتر نیست؟

پس برای ساختن یه تابع باید این کارها رو انجام بدیم:

  • از کلیدواژه function استفاده می‌کنیم
  • اسم تابع رو انتخاب می‌کنیم و می‌نویسیم
  • توی پرانتز جلوش، پارامترها رو می‌نویسیم (اگه تابع پارامتری نداشت، پرانتزها خالی می‌مونن)
  • توی براکت جلوش، کدی که قراره با فراخونده شدن تابع اجرا بشه رو می‌نویسیم
  • هر جایی که خواستیم، با استفاده از اسمِ تابع، تابع رو فراخونی می‌کنیم و پارامترهاشو می‌نویسیم

برگردوندن «مقدار»

یه کاری که تابع‌ها می‌تونن انجام بدن و برنامه‌نویس‌ها زیاد ازش استفاده می‌کنن اینه که یه محاسباتی رو انجام بدن و در نهایت یه «مقدار»ی رو برگردونن. مثلاً p5.js یه تابع آماده‌ای داره به نام random که وقتی فراخونی می‌شه به صورت تصادفی یه عدد به ما برمی‌گردونه و ما می‌تونیم این عدد تصادفی رو داخل یه متغیر ذخیره کنیم (اگه نمی‌دونید متغیر چیه باید یه نگاهی به مطلب قبلی که درباره متغیرها نوشتم بندازید):

const randomNumber = random(1, 10)

اگه مثل من به تولید تصاویر جِنِریتیو علاقه‌مند باشید باید بدونید که قراره خیلی از این تابع random استفاده کنید، چون اساساً هنر مولد و تصاویر جِنِریتیو معمولاً بر اساس مقادیر تصادفی به وجود میان. به شکل‌های مختلف می‌شه از تابع random استفاده کرد؛ مثلا:

  • اگه هیچ عددی به عنوان پارامتر بهش ندید: یه عدد تصادفی بین صفر و یک برمی‌گردونه
  • اگه یک عدد به عنوان پارامتر بهش بدید: یه عدد تصادفی بین صفر و اون عدد برمی‌گردونه
  • اگه دو تا عدد به عنوان پارامتر بهش بدید: یه عدد تصادفی بین اون دو عدد برمی‌گردونه

البته اینایی که گفتم همه قابلیت‌های این تابعِ شگفت‌انگیز نیست؛ حتماً صفحه مربوط به تابع random توی رفرنس p5.js رو بخونید تا اطلاعات کاملی به دست بیارید.

چطوری خودمون یه تابع بسازیم که «مقدار» برگردونه؟

برای این کار باید پارامترهایی که نیاز داریمو مشخص کنیم، کدی که قراره محاسبه رو انجام بده بنویسیم و در نهایت از کلیدواژه return استفاده کنیم تا مقدار مد نظرمون رو به دست بیاریم:

function setup() {
  
     console.log(sum(3, 4, 5))
     // 12
  
}
function draw() {

}
function sum(a, b, c) {
     return a + b + c
}

یه تابع اگه فراخونده نشه اثری روی برنامه نداره؛ در واقع تابع‌ها بعد از تعریف شدن حتماً باید یه جایی استفاده بشن، وگرنه تعریف شدنشون بی‌فایده بوده. ممکنه براتون عجیب باشه که چرا تابع‌های setup و draw که توی برنامه‌های p5.js وجود دارن، بدون فراخونده شدن اجرا می‌شن؟ این به خاطر معماری این کتابخونه‌س؛ به دلایلی سازنده‌های p5.js تصمیم گرفتن که این طوری کار کنه و توی رفرنس رسمی p5.js هم نوشته شده که تابع‌های setup و draw نباید جایی از برنامه فراخونده بشن.

چرخش و جابجا کردن شکل‌ها

توی مثال بعد می‌خوام از تابع‌های آماده p5.js استفاده کنم و یه شکلی رو بچرخونم؛ ببینید این کار چطوری انجام می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight)
     rectMode(CENTER)
     angleMode(DEGREES)
     noStroke()
}
function draw() {
     background(220)
     fill(237, 34, 93)

     // rotating the shape
     translate(windowWidth/2, windowHeight/2)
     rotate(45)
     rect(0, 0, 100, 100)

}
یک مربع که چرخیده شده است.

از پارامتر CENTER توی تابع rectMode استفاده کردم تا مشخص کنم مربع باید از وسط شروع به کشیده شدن کنه؛ توی p5.js مستطیل‌ها و مربع‌ها به صورت پیش‌فرض از گوشه خودشون شروع به کشیده‌شدن می‌کنن، برای همین من از rectMode استفاده کردم تا مربع از وسط خودش کشیده بشه.

همچنین از پارامتر DEGREES توی تابع angleMode استفاده کردم تا مشخص کنم زاویه‌ها با چه واحدی اندازه‌گیری بشن؛ توی p5.js زاویه‌ها به صورت پیش‌فرض از واحد رادیان استفاده می‌کنن، که من اونو به درجه یا DEGREES تغییر دادم.

از تابع translate استفاده کردم تا نقطه مبدأ (یا همون صفر و صفر) رو به وسط صفحه منتقل کنم؛ این یعنی وقتی یه شکل کشیده بشه، اون شکل در وسط صفحه قرار می‌گیره. بعد از تابع rotate استفاده کردم تا شکل رو بچرخونم و در نهایت، خود مربع رو کشیدم؛ دقت کنید همون طوری که قبل‌تر اشاره کردم، تابع translate نقطه صفر و صفر بوم شما رو از بالا سمت چپ جابجا می‌کنه و به نقطه جدیدی که شما تعیین می‌کنید تغییر می‌ده؛ از این جا به بعد، همه مختصاتی که وارد می‌کنید نسبت به این نقطه صفر و صفر جدید تعیین می‌شه؛ برای همینه که تابع rotate بعد از تابع translate فراخونی شده، چون چرخش باید نسبت به مبدأ مختصات جدید انجام بشه وگرنه نتیجه‌ای که مورد نظر ما بوده به دست نمیاد؛ برای این که بهتر متوجه بشید، فرض کنید می‌خوایم چهار تا مربع جدید اطراف این مربع بکشیم. دیگه لازم نیست مربع‌های بعدی رو بچرخونید، بلکه فقط کافیه مختصات مربع‌های جدید رو نسبت به مبدأ مختصات مشخص کنید:

function setup() {
     createCanvas(windowWidth, windowHeight)
     rectMode(CENTER)
     angleMode(DEGREES)
     noStroke()
}
function draw() {
     background(220)
     fill(237, 34, 93)

     // rotating the shape
     translate(windowWidth/2, windowHeight/2)
     rotate(45)
     rect(0, 0, 100, 100)
     rect(110, 0, 100, 100)
     rect(0, 110, 100, 100)
     rect(0, -110, 100, 100)
     rect(-110, 0, 100, 100)

}
چند مربع که چرخیده شده اند.

پوش و پاپ

حالا اگه دوست نداشتیم این طور باشه چی؟ یعنی بخوایم یه بخش از کد، فارغ از تنظیمات بخش‌های دیگه کار کنه چی؟ در این صورت باید اون بخش رو بین تابع‌های push و pop بنویسیم؛ این طوری برای کدی که بین این دو تابع نوشته می‌شه یه وضعیت جدید به وجود میاریم؛ دقت کنید که این دو تابع هم از توابع آماده p5.js هستن:

function setup() {
     createCanvas(windowWidth, windowHeight)
     rectMode(CENTER)
     angleMode(DEGREES)
     noStroke()
}
function draw() {
     background(220)
     fill(237, 34, 93)

     // rotating the shape
     push()
     translate(windowWidth/2, windowHeight/2)
     rotate(45)
     rect(0, 0, 100, 100)
     pop()
  
     // new squares
     rect(110, 0, 100, 100)
     rect(0, 110, 100, 100)
     rect(0, -110, 100, 100)
     rect(-110, 0, 100, 100)

}
چند مربع در صفحه به شکل پراکنده قرار گرفته اند.

همون طور که می‌بینید، تنظیماتی که بین تابع‌های push و pop نوشته شده، روی مربع‌های دیگه اعمال نشده و اصطلاحاً این بخش از کد، ایزوله شده. تابع‌های push و pop همیشه با همدیگه استفاده می‌شن؛ استفاده کردن از یکی از این توابع و استفاده نکردن از اون یکی، هیچ معنایی نداره و تأثیری هم توی برنامه‌تون نمی‌ذاره.

کشیدن 7 شکل در p5.js

چه چیزایی می‌کشیم؟

p5.js شامل گروهی از تابع‌هاس که می‌شه باهاشون شکل کشید؛ توی این درس با 6 تا از این توابع آشنا می‌شیم تا بتونیم این اشکال رو خلق کنیم:

  • مثلت
  • چهارضلعی
  • مربع و مستطیل
  • دایره
  • اشکال دلخواه
  • آرک
  • اسپلاین

با ترکیب شکل‌های ساده می‌شه شکل‌های پیچیده‌تر ساخت. قبل از این که بریم سراغ شکل‌های پیچیده، ببینیم چطوری می‌شه یه خط ساده کشید؟ می‌دونیم که خط، کوتاه‌ترین فاصله بین دو نقطه رو به ما نشون می‌ده؛ پس برای کشیدن یه خط نیاز به چهار پارامتر داریم: دو تا برای مشخص کردن نقطه شروع و دو تا برای مشخص کردن نقطه پایان:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(200)
     line(30, 50, 300, 100)
}
کشیدن یک خط توسط p5.js

بر اساس همین الگو، کشیدن یه مثلث نیاز به شش پارامتر داره، چون داره سه تا نقطه رو به هم وصل می‌کنه و کشیدن یه چهارضلعی نیاز به هشت پارامتر داره چون داره چهار تا نقطه رو بهم وصل می‌کنه:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(200)
     quad(158, 55, 199, 14, 392, 66, 351, 107)
     triangle(347, 54, 392, 9, 392, 66)
     triangle(158, 55, 290, 91, 290, 112)
}
کشیدن چند شکل توسط توابع p5.js

برای کشیدن مربع و دایره، به ترتیب از تابع‌های rect و ellippse استفاده می‌کنیم که هر جفتشون نیاز به چهار پارامتر دارن: اولی و دومی x و y هستن که محل قرارگیری شکل رو مشخص می‌کنن، سومی عرضِ شکله و چهارمی ارتفاع:

function setup() {
     createCanvas(windowWidth, windowHeight)
     rectMode(CENTER)
}
function draw() {
     background(200)
     rect(windowWidth/2, windowHeight/2, 300, 300)
}

ترتیب کشیده شدن شکل ها

وقتی برنامه شما اجرا می‌شه، مرورگر از اولین خطِ کد شما شروع می‌کنه و به ترتیب پایین میاد و به آخر می‌رسه. اگه بخواید یه شکل روی همه شکل‌های دیگه کشیده بشه، باید توی کُدتون اون شکل رو بعد از همه شکل‌ها بکشید. ببینید:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(200)
     ellipse(300, 300, 200, 200)
     rect(300, 300, 250, 30)
}
شکل مستطیل روی شکل دایره قرار گرفته است

کشیدن شکل دل‌خواه

برای کشیدن شکل توی p5.js محدود به این تابع‌هایی که تا این جا دیدید نیستید. تابع beginShape برای شروع کشیدن یه شکلِ جدیدِ دلخواه استفاده می‌شه، تابع vertex یه رأس ساده درست می‌کنه و مختصات x و y اون رأس رو توی خودش تعریف می‌کنه و در نهایت، تابع endShape برای نشون دادن پایان یه شکل استفاده می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(200)
     translate(0, 100)

     // a new shape
     beginShape()
     vertex(180, 82)
     vertex(207, 36)
     vertex(214, 63)
     vertex(407, 11)
     vertex(412, 30)
     vertex(219, 82)
     vertex(226, 109)
     endShape(CLOSE)
  
}
کشیدن شکل های دلخواه در p5.js

شکلِ آرک

برای کشیدن منحنی‌ها توی p5.js چند تا راه داریم. ساده‌ترین منحنی‌ها آرک‌ها هستن که بخشی از دایره یا بیضی رو تشکیل می‌دن. تابع arc شش تا پارامتر داره: اولی و دومی مختصات محل قرارگیری شکل، سومی و چهارمی عرض و ارتفاع شکل، پنجمی و ششمی هم زاویه شروع و پایانِ کشیده شدن شکل هستن:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(200)
     arc(90, 60, 80, 80, 0, HALF_PI)
     arc(190, 60, 80, 80, 0, PI+HALF_PI)
     arc(290, 60, 80, 80, PI, TWO_PI+HALF_PI)
     arc(390, 60, 80, 80, QUARTER_PI, PI+QUARTER_PI)
}
شکل آرک در p5.js

توی p5.js زاویه‌ها به صورت پیش‌فرض بر اساس رادیان تعریف می‌شن. رادیان، واحد اندازه‌گیری زاویه بر اساس عدد پای، یا همون پی، هست. چهار تا از مقادیر رادیان هستن که کاربردشون زیاده و برای همین توی p5.js اسامی خاصی براشون در نظر گرفته شده؛ این مقادیر PI و QUARTER_PI و HALF_PI و TWO_PI هستن.

شکلِ اسپلاین

کشیدن آرک کار راحتیه ولی اگه بخوایم منحنی‌هایی بکشیم که بخشی از یه دایره یا بیضی نیستن چی؟ تابع curveVertex بهمون اجازه می‌ده این کارو بکنیم و یه اسپلاین بکشیم؛ برای این کار باید دوباره از تابع‌های beginShape و endShape استفاده کنیم و با استفاده از تابع curveVertex مختصات نقاط منحنی رو مشخص کنیم:

function setup() {
     createCanvas(windowWidth, windowHeight);
     background(200);
     noFill();
     stroke(0);
}
function draw() {
     beginShape();
     curveVertex(40, 40);
     curveVertex(40, 40);
     curveVertex(80, 60);
     curveVertex(100, 100);
     curveVertex(60, 120);
     curveVertex(50, 150);
     curveVertex(50, 150);
     endShape();
}
کشیدن شکل اسپلاین در p5.js

هزارتو

تا این جا چیزای ساده‌ای درباره برنامه‌نویسی یاد گرفتیم: یاد گرفتیم چطوری از متغیرها استفاده کنیم، چطوری تابع بنویسیم و چطوری از تابع‌های آماده توی p5.js استفاده کنیم تا شکل‌های مختلف درست کنیم. حالا وقتشه که از همه چیزایی که یاد گرفتیم استفاده کنیم.

توی این اسکیس می‌خوام یه برنامه بنویسم که هر بار اجرا می‌شه با استفاده از خطوط مورّب یه هزارتوی تصادفی درست کنه. قبل از هر چیز دو تا خط مورّب می‌کشم که اجزای اصلی سازنده هزارتو هستن و یه متغیر می‌سازم که اندازه هر خط رو توی خودش ذخیره کنه:

let x = 0
let y = 0
let lineSize = 50

function setup() { 
     createCanvas(windowWidth, windowHeight)
     background(220)
     strokeWeight(5)
}
function draw() {
     line(x, y, lineSize, lineSize)
     line(lineSize, y, x, lineSize)
}
دو خط به شکل ضربدر کشیده شده اند

ویژگی اصلی یک اثر مولد (Generative Art) اینه که هر بار کد اجرا می‌شه و خروجی رو به نمایش می‌ذاره، به شکل تصادفی بخش‌هایی از اثر تغییر می‌کنه. توی این پروژه هم می‌خوام هر بار که برنامه اجرا می‌شه یه هزارتوی جدید تولید بشه. می‌خوام هر بار که draw اجرا می‌شه به صورت تصادفی فقط یکی از این خطوط مورب کشیده بشه؛ برای این کار یه متغیر می‌سازم به نام leftOrRight و با استفاده از تابع random توش یه عدد تصادفی بین صفر و یک ذخیره می‌کنم؛ بعد یه شرط می‌نویسم که چک کنم این عدد بزرگ‌تر از نیمه یا کوچک‌تر از نیم:

let x = 0
let y = 0
let lineSize = 50

function setup() { 
     createCanvas(windowWidth, windowHeight)
     background(220)
     strokeWeight(5)
}
function draw() {

     // random
     const leftOrRight = random()
     if (leftOrRight> 0.5) {
          line(x, y, x+lineSize, y+lineSize)
     } else {
          line(x, y+lineSize, x+lineSize, y)
     }
     x += lineSize
     
}
با هر بار فراخوانی کد یک خط به شکل تصادفی کشیده می شود

حالا فقط باید یه شرط بذارم و چک کنم که اگه x از عرض صفحه بیشتر شده، از خط بعدی شروع به کشیدن شکل‌ها کنه:

let x = 0
let y = 0
let lineSize = 50

function setup() { 
     createCanvas(windowWidth, windowHeight)
     background(220)
     strokeWeight(5)
}
function draw() {

     // random
     var leftOrRight = random();
     if (leftOrRight > 0.5) {
          line(x, y, x+lineSize, y+lineSize)
     } else {
          line(x, y+lineSize, x+lineSize, y);
     }
     x += lineSize

     // fill next line
     if(x > windowWidth) {
          x = 0
          y += lineSize
     }
     
}
کشیده شدن یک هزارتو

اگه دوست داشته باشید می‌تونید با متغیرهایی که توی این کد هست بازی کنید و ببینید خروجی کُد چطوری تغییر می‌کنه. مثلاً الان احتمال این که خط‌ها از چپ به راست یا از راست به چپ کشیده بشن رو نیم یا همون پنجاه‌درصد گذاشتیم؛ اگه احتمال کشیده شدن یکی از این دو نوع خط رو افزایش بدیم شکلمون خیلی تغییر می‌کنه:

با ویرایش بعضی از خطوط کد، شکل خروجی ممکن است به بسیار متفاوت شود

آبجکت

تا الان خیلی چیزا درباره جاوااسکریپت یاد گرفتیم و الان قراره درباره آبجکت توی جاوااسکریپت یاد بگیریم. اگه یادتون باشه یه فهرست داشتیم از انوع داده توی جاوااسکریپت، بیاید یه بار دیگه با هم مرورش کنیم:

  • عدد ـ Number ـ ممکنه عدد صحیح یا اعشاری باشه
  • استرینگ ـ String ـ همون متنه و بین کوتیشن‌مارک یا بک‌تیک قرار می‌گیره
  • بولیَن ـ Boolean ـ یعنی true یا false (صحیح یا غلط)
  • نال ـ Null ـ مقدارِ خالی
  • تعریف‌نشده ـ Undefined ـ متغیر تعریف شده ولی هیچ مقداری نداره
  • آرایه ـ Array ـ فهرستی از داده‌ها
  • تابع ـ Function
  • آبجکت ـ Object

آبجکت آخرین نوع از انواع داده‌س که می‌شه توی جاوااسکریپت تعریف کرد و در واقع خودش یه روشی برای ساختارمند کردن داده‌هاس. آبجکت‌ها به شما کمک می‌کنن تا به کدتون نظم بدید و فرایند برنامه‌نویسی رو آسون‌تر کنید. چند تا راه برای ساختن یه آبجکت وجود داره؛ یکی‌ش اینه که یه متغیر بسازیم و از علامت آکولاد استفاده کنیم (توی انگلیسی بهش می‌گن کِرلی‌برِیس، برنامه‌نویس‌ها توی زبان عامیانه بهش می‌گن سیبیل):

let movie = {}

این آکولادی الان ساختیم، یه آبجکتِ خالیه که داخل متغیرمون ذخیره شده؛ حالا می‌تونیم بهش یه ویژگی‌هایی نسبت بدیم و برای هر ویژگی یه «مقدار» تعریف کنیم؛ این طوری:

let movie = {}
movie.title = 'Dune'
movie.rating = 8
movie.director = 'Denis Villeneuve'

console.log(movie)
مشخصات یک آبجکت

یه روش راحت‌تر برای درست کردن آبجکت اینه که از همون اول همه این ویژگی‌ها و مقدارها رو توی خود آکولاد تعریف کنیم:

let movie = {
     title: 'Dune',
     rating: 8,
     director: 'Denis Villeneuve'
}

همون طور که می‌بینید، آبجکت‌ها شامل جفت‌هایی از اطلاعات هستن که بهش می‌گن «کلید ـ مقدار» یا key-value. مثلاً توی آبجکت بالا که مشخصات یه فیلمو نشون می‌ده، کلید‌ها title و rating و director هستن و مقدارهاشونو 'Dune' و 8 و 'Denis Villeneuve' تعریف کردم. توی هر آبجکت، هر «کلید»ی مسئول نگه‌داری یه «مقدار»ه و هر کدوم از این جفت‌ها، یه «ویژگی» از آبجکت هستن. برای دسترسی به یه مقدار توی یه آبجکت، باید از کلید اون مقدار استفاده کنیم:

let movie = {
     title: 'Dune',
     rating: 8,
     director: 'Denis Villeneuve'
}

console.log(movie.title)
// Dune

مِتُدِ آبجکت

توی آبجکت‌ها می‌شه تابع هم تعریف کرد، تابع‌هایی که مخصوص همون آبجکت هستن و با داده‌های داخل اون آبجکت کار می‌کنن؛ در این صورت به اون ویژگی از آبجکت که شامل یه تابعه اصطلاحاً می‌گن مِتُدِ اون آبجکت. با همین آبجکتی که تا الان ساختیم، می‌خوام یه مِتُد تعریف کنم و از اون متد استفاده کنم:

let movie = {
     title: 'Dune',
     rating: 8,
     director: 'Denis Villeneuve',
     myFavoriteMovie: function () {
          console.log('My favorite movie is ' + this.title)
     }
}
movie.myFavoriteMovie()

همون طور که توی مثال بالا دیدید، برای این که توی متُد از کلیدی که داخل آبجکت تعریف شده استفاده کنم، از کلیدواژه this استفاده کردم و بعد متُدی که ساختمو فراخونی کردم تا عملیاتی که داخل متُد نوشته شده، انجام بشه؛ دقت کنید که چون داریم یه تابع رو فراخونی می‌کنیم باید بعد از نوشتن اسم اون کلید، از پرانتز استفاده کنیم.

ایده‌ی اصلی آبجکت توی جاوااسکریپت یا هر زبون برنامه‌نویسیِ دیگه، اینه که برنامه‌نویس بتونه از روی اشیای دنیای واقعی، توی برنامه‌ش آبجکت‌هایی رو تعریف کنه که ویژگی‌ها و رفتارهای خاصی دارن. حالا یه تست کنیم ببینیم چطوری می‌شه توی p5.js از آبجکت‌ها استفاده کرد. یه آبجکت درست می‌کنم که ویژگی‌های یه دایره رو داشته باشه و بتونه برامون یه دایره بکشه:

let myCircle

function setup() {
     createCanvas(windowWidth, windowHeight)
     myCircle = {
          x: width/2,
          y: height/2,
          size: 50,
          draw: function() {
               ellipse(this.x, this.y, this.size, this.size)
          }
     }
}
function draw() {
     myCircle.draw()
}

ممکنه پیش خودتون فک کنید این بی‌خاصیت‌ترین کاریه که می‌شه با یه آبجکت انجام داد چون خیلی راحت می‌شد توی draw از تابع آماده ellipse استفاده کرد و این همه دردسر هم برای ساختن یه آبجکت نکشید. اما این تازه شروع کاره. الان یه متُدِ دیگه اضافه می‌کنیم تا هر وقت فراخونی شد یه واحد اندازه دایره رو افزایش بده:

let myCircle

function setup() {
     createCanvas(windowWidth, windowHeight)
     myCircle = {
          x: width/2,
          y: height/2,
          size: 50,
          draw: function() {
               ellipse(this.x, this.y, this.size, this.size)
          },
          grow: function() {
               if(this.size < 200) {
                    this.size += 1
               }
          }
     }
}
function draw() {
     strokeWeight(3)
     myCircle.draw()
     myCircle.grow()
}

تابع سازنده

یه راه دیگه برای ساختن آبجکت‌ها استفاده کردن از روش تابعِ سازنده‌س؛ این بار موقع تعریف کردن آبجکت، از یه تابع استفاده می‌کنیم؛ این طوری:

let MyCircle
let circle1

function setup() {
     createCanvas(windowWidth, windowHeight)

     // 1. a constructor function
     MyCircle = function () {
          this.x = windowWidth/2
          this.y = windowHeight/2
          this.size = 50
          this.draw = function() {
               ellipse(this.x, this.y, this.size, this.size)
          }
          this.grow = function() {
               if(this.size < 200) {
                    this.size += 1
               }
          }
     }

     // 2. new circle object
     circle1 = new MyCircle()
  
}
function draw() {

     // 3. using the object
     circle1.draw()

}

توی جاوااسکریپت به تابعی که داره یه آبجکت تولید می‌کنه، اصطلاحاً می‌گن تابعِ سازنده. تابعِ سازنده تابعیه که مثل یه قالب عمل می‌کنه برای تولید آبجکت‌هایی که ویژگی‌هاشونو از تابعِ سازنده‌شون به ارث می‌برن. توی مثال بالا توی مرحله اول، یه تابع سازنده تعریف شده و توی متغیر MyCircle قرار گرفته. بعد با کلیدواژه new از تابعِ سازنده یه آبجکت ساختیم و در نهایت، توی مرحله سوم از اون آبجکت استفاده کردیم. هر وقت نیاز باشه که از تابعِ سازنده یه آبجکتِ جدید بسازیم، باید از کلیدواژه new قبل از اسمِ اون تابع استفاده کنیم؛ زیبایی این روش اینه که می‌تونیم از یه قالبِ ثابت، تعداد زیادی آبجکت بسازیم که هر کدوم ویژگی‌های مختلف و متنوعی دارن. ببینید:

let MyCircle
let circle1
let circle2
let circle3

function setup() {
     createCanvas(windowWidth, windowHeight)

     // a constructor function
     MyCircle = function () {
          this.x = windowWidth/2
          this.y = windowHeight/2
          this.size = 50
          this.draw = function() {
               ellipse(this.x, this.y, this.size, this.size)
          }
          this.grow = function() {
               if(this.size < 200) {
                    this.size += 1
               }
          }
     }

     // new circle object
     circle1 = new MyCircle()
     circle2 = new MyCircle()
     circle3 = new MyCircle()
  
}
function draw() {
     circle2.x = windowWidth/2
     circle1.draw()
     circle2.x = windowWidth/2 - 100
     circle2.draw()
     circle3.x = windowWidth/2 + 100
     circle3.draw()
}

توی مثال بالا از طریق کلیدواژه new سه تا آبجکت مختلف از تابعِ سازنده‌ی MyCircle به وجود آوردیم و توی draw ویژگی‌هاشونو تغییر دادیم؛ یه نکته خیلی مهم این جا وجود داره: اگه دقت کرده باشید، من برای نام‌گذاری تابع سازنده، با حرفِ بزرگ شروع به نوشتن کردم؛ این کار اجباری نیست ولی بازم یه جور قرارداده برای انتخاب نامِ توابعِ سازنده؛ این طوری به خودمون یادآوری می‌کنیم که این تابع، یه تابعِ سازنده‌س و برای استفاده کردنش باید از کلیدواژه new استفاده بشه.

3 روش نوشتن حلقه در جاوااسکریپت

یکی از کارایی که کامپیوترها خوب انجام می‌دن، تکرار کردنه. فرض کنید قراره هزار تا شکل با ویژگی‌های مختلف توی برنامه‌تون بسازید؛ برای چنین مواردی که قراره یه کاری به دفعات زیادی تکرار کنیم، توی برنامه‌نویسی از ساختاری به نام حلقه یا لوپ استفاده می‌کنیم که از مهم‌ترین مفاهیم برنامه‌نویسی هستن و می‌تونیم باهاش یه تیکه کُد رو بارها و بارها تکرار کنیم.

ما که داریم برنامه‌نویسی رو با p5.js یاد می‌گیریم، از قبل با مفهوم حلقه آشنا هستیم: تابع draw یه لوپه که داره یه تیکه کُد رو همین طوری پشت سر هم تکرار می‌کنه تا وقتی که برنامه بسته بشه؛ اما خودمون چطوری می‌تونیم چنین لوپی بنویسیم؟

روش اول: ساختن حلقه فور

توی جاوااسکریپت برای به ساختن یه لوپ چند تا راه وجود داره. من معمولاً از for استفاده می‌کنم. برنامه ما با استفاده از for می‌تونه یک عملیاتو به دفعاتی که خودمون مشخص می‌کنیم، تکرار کنه. یه لوپِ for از پنج عنصر تشکیل می‎شه:

  • کلیدواژه for ـ دربرگیرنده کلِ حلقه
  • متغیر اولیه ـ یه جور شمارنده که معمولاً از 0 شروع می‌شه
  • شرط ـ شرطی که تا وقتی true باشه لوپ ادامه پیدا می‌کنه
  • به‌روزرسانی متغیر اولیه ـ آخر هر تکرار، متغیر اولیه به‌روز می‌شه
  • عملیات ـ اتفاق‌هایی که توی هر بار تکرار حلقه می‌افته
function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {
     background(200)
     fill(220, 30, 90)
     noStroke()

     // a for loop
     for(let i = 0; i < 10; i++) {
          ellipse(i*50, 0, 50, 50)
     }

}
کشیدن دایره با استفاده از حلقه

توی مثالِ ساده‌ی بالا می‌تونید پنج مرحله ساختن یه حلقه رو ببینید:

  • (اول) با کلیدواژه for یه حلقه ساختیم
  • (دوم) با کلیدواژه let یه متغیر تعریف کردیم و مقدارشو 0 قرار دادیم
  • (سوم) شرط ادامه حلقه رو کوچک‌‌تر از 10 بودن متغیر اولیه مشخص کردیم
  • (چهارم) تعیین کردیم که بعد از هر بار تکرار حلقه، یه واحد به متغیر اولیه اضافه بشه
  • (پنجم) عملیاتی که حلقه قراره انجام بده رو مشخص کردیم که کشیدن یه دایره‌س

وقتی برنامه رو اجرا کنید می‌بینید که ده تا دایره روی صفحه کشیده می‌شه؛ چون یازدهمین بار که حلقه اجرا می‌شه، متغیر اولیه‌ی ما 11 شده و در این صورت شرط حلقه، یعنی i < 10 دیگه true نیست، حلقه بسته می‌شه و تمام.

روش دوم: ساختن حلقه‌ با استفاده از آرایه

آرایه‌ها رو که یادتون هست؟ یه راه دیگه برای ساختن حلقه‌ی for استفاده کردن از یه آرایه‌س؛ توی این حالت، نوشتن حلقه یه خرده ساده‌تره و دیگه نیازی به تعریف کردن یه متغیر و این جور کارا ندارید؛ ببینید چطور نوشته می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight);

     // a for loop
     const planets = ['mercury', 'venus', 'earth']
     for(planet of planets) {
          console.log(planet)
     }

}
function draw() {
  
}
ساختن حلقه با استفاده از آزایه

روش سوم: ساختن حلقه وایل

یه جور حلقه دیگه هم هست به نام while که تا زمانی که یه شرط true باشه تکرار می‌شه. توی مثال پایین این قدر حلقه تکرار می‌شه که متغیر myValue به عدد صد برسه؛ توی هر بار اجرا شدن حلقه هم عدد 10 به myValue اضافه می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight);

     // a while loop
     let myValue = 0
     while(myValue < 100) {
          myValue += 10
          console.log('The value of myValue is ' + myValue)
     }

}
function draw() {
  
}
ساختن حلقه وایل

در عمل حلقه‌ها به چه دردی می‌خورن؟

حالا که با حلقه‌ها آشنا شدیم می‌تونیم ببینیم توی یه پروژه واقعی چطوری می‌شه از حلقه‌ها استفاده کرد. از یه مثال ساده شروع می‌کنم: توی این اسکیس می‌خوام یه مربع بکشم و توشو هاشور بزنم.

const rectWidth = 100

function setup() {
     createCanvas(windowWidth, windowHeight)
     rectMode(CENTER)
     background(200)
     fill(235)
     noLoop()
}
function draw() {
  
     // square
     rect(windowWidth/2, windowHeight/2, rectWidth)

     // hatch
     translate(windowWidth/2 - rectWidth/2, windowHeight/2 - rectWidth/2)
     for(let i = 0; i < rectWidth; i += 10) {
          line(i, 0, 0, i)
     }
     for(let i = 0; i < rectWidth; i += 10) {
          line(rectWidth, i, i, rectWidth)
     }

}
یک مربع در وسط تصویر

ببینیم توی مثال بالا دقیقاً چه اتفاقی افتاده:

اول وسط بوم یه مربع کشیدم؛ برای این کار از متغیری به نام rectWidth استفاده کردم که در واقع اندازه مربعی که قراره کشیده بشه رو مشخص می‌کنه. می‌تونید تغییرش بدید و نتیجه رو ببینید. همچنین از پارامتر CENTER توی تابع rectMode استفاده کردم که باعث می‌شه مربع از مرکز خودش جانمایی بشه.

بعد با استفاده از تابع translate نقطه صفر و صفر بوم رو به گوشه مربع منتقل می‌کنم.

حالا باید از حلقه for استفاده کنم تا با فاصله ده پیکسل ده پیکسل و با استفاده از تابع line نقاط روی اضلاع مربع رو به هم وصل کنم. همون طور که می‌بینید برای کشیدن خطوط نیمه بالای مربع از یه حلقه استفاده کردم و برای کشیدن خطوط نیمه پایین مربع، از یه حلقه دیگه.

توی اسکیس بعدی می‌خوام از یه حلقه for استفاده کنم و یه ستاره بکشم:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(255)
     strokeWeight(10)

     // move canvas origin to center
     translate(width / 2, height / 2)

     // number of lines
     const circleResolution = int(map(mouseY, 0, height, 1, 15))

     // line length
     const radius = mouseX - width

     // spread lines in a circular shape 
     const angle = TWO_PI / circleResolution

     // loop
     for (let i=0; i<=circleResolution; i++) {
          const x = cos(angle * i) * (radius/5);
          const y = sin(angle * i) * (radius/5);
          line(0, 0, x, y);
     }
  
}
یک ستاره که با استفاده از یک حلقه به وجود آمده است

اگه این کد رو اجرا کنید می‌بینید که اندازه و تعداد پرهای ستاره با حرکت دادن ماوس روی صفحه تغییر می‌کنه؛ قبلاً یاد گرفتیم که هر کدی که توی تابع draw نوشته بشه توی هر فریم یک بار تکرار می‌شه و برای همینه که ما می‌تونیم چیزهای مختلفو روی مانیتور به حرکت دربیاریم.

توی اسکیس بالا هم از تابع translate استفاده کردم تا نقطه صفر و صفرِ بوم رو به وسط صفحه منتقل کنم. بعد از تابع map استفاده کردم و تعداد پَرهای ستاره رو نسبت به موقعیت ماوس توی متغیر circleResolution ذخیره کردم. بعد بازم بر اساس موقعیت ماوس طول پَرهای ستاره رو توی متغیر radius ذخیره کردم. بعد بر اساس تعداد پَرهای ستاره، زاویه بین هر پَر ستاره رو مشخص کردم و در نهایت یه لوپ نوشتم تا پَرهای ستاره رو بر اساس متغیرهایی که تعریف کردم بکشه.

می‌تونیم به جای کشیدن این خطوط، از تابع vertex که قبلاً یادگرفتیم استفاده کنیم و شکل‌های مختلف تولید کنیم:

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     background(255)
     strokeWeight(3)
     stroke(0, 25)
  }
  
function draw() {
     if (mouseIsPressed && mouseButton == LEFT) {
          push()
          translate(windowWidth / 2, windowHeight / 2)
          const circleResolution = int(map(mouseY + 100, 0, windowHeight, 3, 10))
          const radius = mouseX - width / 2
          const angle = TWO_PI / circleResolution

          // shape
          beginShape()
          for (let i = 0; i <= circleResolution; i++) {
               const x = cos(angle * i) * radius
               const y = sin(angle * i) * radius
               vertex(x, y)
          }
          endShape()

          pop()
     }
}

حلقه های تو در تو

توی حلقه‌ها می‌شه دوباره حلقه نوشت؛ به این الگو می‌گن حلقه‌های تودرتو؛ این الگو خیلی رایجه و معمولاً برای درست کردن یه شبکه استفاده می‌شه؛ مثلاً به این اسکیس نگاه کنید؛ توی این اسکیس با شکل دایره یه شبکه روی بوم می‌سازیم که تعداد خونه‌های این شبکه توسط متغیر tileCount مشخص می‌شه و شعاع هر دایره بر اساس موقعیت مکانی ماوس محاسبه می‌شه:

const tileCount = 15
const circleAlpha = 130

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {
     background(255)
     randomSeed(0)
     strokeWeight(5)
     translate(windowWidth / tileCount / 2, windowHeight / tileCount / 2)
  
     for (let gridY = 0; gridY < tileCount; gridY++) {
          for (let gridX = 0; gridX < tileCount; gridX++) {

               const posX = windowWidth / tileCount * gridX
               const posY = windowHeight / tileCount * gridY
               ellipse(posX, posY, mouseY / tileCount)
    
          }
     }
}

بی‌نظمی مکعبی

این یکی یه اسکیسِ معروفه به نام «بی‌نظمی مکعبی» که با الگوی حلقه‌های تودرتو نوشته می‌شه؛ این مورد رو با همدیگه به صورت قدم به قدم می‌نویسیم: با یه اسکیس ساده شروع می‌کنیم که شامل setup و draw باشه و از noLoop استفاده می‌کنیم چون یه اسکیس استاتیک یا ایستا داریم درست می‌کنیم که نیازی به انیمیشن نداره؛ یه متغیر هم نیاز داریم برای ذخیره اندازه مربع‌ها:

const squareSize = 50

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     noLoop()
     background(220)
}
function draw() {
     
}

مرحله بعدی اینه که یه چیزی بکشیم. می‌خوام همه صفحه رو با مربع پُر کنم. سایز مربع‌ها بر اساس متغیر squareSize مشخص می‌شه. از تابع‌های push و translate و pop داخل حلقه‌های تودرتو استفاده می‌کنم تا ردیف‌ها و ستون‌های شبکه رو تعریف کنم و با تابع rect مربع‌ها رو می‌کشم:

const squareSize = 50

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     noLoop()
     strokeWeight(3)
     background(220)
}
function draw() {
     for(let x = squareSize*2; x <= width - squareSize*2; x += squareSize) {
          for(let y = squareSize*2; y <= height - squareSize*2; y += squareSize) {
               push()
	             translate(x, y)
               rect(-squareSize/2, -squareSize/2, squareSize)
               pop()
          }
     }
}

حالا می‌خوایم نظمشونو به هم بزنیم، طوری که هر چی به سمت پایین بوم بریم، بی‌نظمی بیشتری رو ببینیم:

const squareSize = 50

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     noLoop()
     strokeWeight(3)
     background(220)
}
function draw() {
     for(let x = squareSize*2; x <= width - squareSize*2; x += squareSize) {
          for(let y = squareSize*2; y <= height - squareSize*2; y += squareSize) {

               // random rotation
               const rotation = y/height * QUARTER_PI/45 * random() * 25;

               // create each square
               push()
               translate(x, y);
               rotate(rotation);
               rect(-squareSize/2, -squareSize/2, squareSize)
               pop()
               
          }
     }
  
}