├── images
└── book
│ ├── 1
│ ├── download_love.png
│ ├── install_brane.png
│ ├── project_brane.png
│ └── download_brane.png
│ ├── 4
│ ├── drawmode.png
│ ├── rectangle.png
│ └── example_rectangle.png
│ ├── 5
│ ├── coordinates.png
│ ├── rectangle.png
│ └── rectangle_move.gif
│ ├── 6
│ ├── rectangle_right.gif
│ └── rectangle_stop.gif
│ ├── 7
│ ├── fruits.png
│ ├── shift.png
│ └── table.png
│ ├── 8
│ └── moving_rectangles.gif
│ ├── 9
│ └── scope.png
│ ├── 11
│ ├── blueprint.png
│ ├── extension.png
│ ├── moving_rectangle_circle.gif
│ └── moving_rectangles_classes.gif
│ ├── 12
│ ├── color.png
│ ├── shear.png
│ ├── sheep.png
│ ├── origin1.png
│ └── origin2.png
│ ├── 13
│ ├── rectangles1.png
│ ├── rectangles2.png
│ └── rectangles3.png
│ ├── 14
│ ├── demo.gif
│ ├── panda.png
│ ├── snake.png
│ └── bullet.png
│ ├── 15
│ ├── compress.png
│ ├── control_panel.png
│ ├── folder_options.png
│ └── personalization.png
│ ├── 16
│ ├── pi.gif
│ ├── angle.gif
│ ├── atan2.png
│ ├── radian.gif
│ ├── arrow_off.png
│ ├── sinecosine.gif
│ ├── triangle.png
│ ├── arrow_right.png
│ ├── pythagorean.png
│ ├── sinecosine2.png
│ ├── sinecosine3.png
│ ├── sinecosine4.png
│ ├── following_arrow.gif
│ ├── following_circle.gif
│ ├── following_circle_distance.gif
│ └── following_circle_distance_speed.gif
│ ├── 17
│ ├── jump.gif
│ ├── jump.png
│ ├── jump1.png
│ ├── jump2.png
│ ├── jump3.png
│ ├── jump4.png
│ ├── jump5.png
│ ├── almost.png
│ ├── jump_2.png
│ ├── jump_3.png
│ ├── bleeding.png
│ ├── jump_help.png
│ ├── bleeding_fix.png
│ ├── rectangles1.png
│ ├── quad_position.png
│ └── whatisgoingon.png
│ ├── 18
│ ├── level.png
│ ├── table.png
│ ├── tile.png
│ ├── colors.png
│ ├── houses.png
│ ├── one_row.png
│ ├── player.png
│ ├── tileset.png
│ ├── 2d_table.gif
│ ├── numbered.png
│ ├── colors_image.png
│ ├── tile-move-1.gif
│ ├── tile-move-2.gif
│ └── multiple_rows.png
│ ├── 19
│ ├── sfx.ogg
│ └── song.ogg
│ ├── 20
│ ├── error1.png
│ ├── error2.png
│ ├── error3.png
│ ├── error4.png
│ ├── error5.png
│ ├── error6.png
│ ├── error7.png
│ └── hendrik.png
│ ├── 21
│ ├── face.png
│ ├── dollar.png
│ ├── coin_grower.gif
│ ├── table_remove.png
│ ├── circles_collision.png
│ └── table_remove_reverse.png
│ ├── 22
│ ├── shake.gif
│ ├── splitscreen.gif
│ ├── camera_center.gif
│ └── camera_center_moving.gif
│ ├── 23
│ ├── box.png
│ ├── loss.png
│ ├── wall.png
│ ├── player.png
│ ├── box_wall_bad.gif
│ ├── box_wall_good.gif
│ ├── collision_checks.png
│ ├── hor_ver_collision.gif
│ └── collision_to_previous.gif
│ ├── 24
│ ├── map.png
│ ├── falling.gif
│ ├── gravity.gif
│ ├── jumping.gif
│ ├── midair.gif
│ ├── through.gif
│ └── platform.png
│ └── bonus
│ └── vscode
│ ├── error.png
│ ├── debugging.png
│ └── lovepath.gif
├── readme.md
├── LICENSE
└── book
├── contents.php
├── chapter15.md
├── chapter19.md
├── chapter10.md
├── chapter0.md
├── chapter3.md
├── chapter2.md
├── chapter5.md
├── chapter8.md
├── chapter1.md
├── chapter4.md
├── chapter12.md
├── bonus
└── vscode.md
├── chapter6.md
├── chapter13.md
├── chapter9.md
├── chapter7.md
├── chapter20.md
├── chapter16.md
├── chapter17.md
├── chapter14.md
├── chapter18.md
├── chapter11.md
└── chapter21.md
/images/book/16/pi.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/pi.gif
--------------------------------------------------------------------------------
/images/book/12/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/12/color.png
--------------------------------------------------------------------------------
/images/book/12/shear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/12/shear.png
--------------------------------------------------------------------------------
/images/book/12/sheep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/12/sheep.png
--------------------------------------------------------------------------------
/images/book/14/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/14/demo.gif
--------------------------------------------------------------------------------
/images/book/14/panda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/14/panda.png
--------------------------------------------------------------------------------
/images/book/14/snake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/14/snake.png
--------------------------------------------------------------------------------
/images/book/16/angle.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/angle.gif
--------------------------------------------------------------------------------
/images/book/16/atan2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/atan2.png
--------------------------------------------------------------------------------
/images/book/17/jump.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump.gif
--------------------------------------------------------------------------------
/images/book/17/jump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump.png
--------------------------------------------------------------------------------
/images/book/17/jump1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump1.png
--------------------------------------------------------------------------------
/images/book/17/jump2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump2.png
--------------------------------------------------------------------------------
/images/book/17/jump3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump3.png
--------------------------------------------------------------------------------
/images/book/17/jump4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump4.png
--------------------------------------------------------------------------------
/images/book/17/jump5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump5.png
--------------------------------------------------------------------------------
/images/book/18/level.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/level.png
--------------------------------------------------------------------------------
/images/book/18/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/table.png
--------------------------------------------------------------------------------
/images/book/18/tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/tile.png
--------------------------------------------------------------------------------
/images/book/19/sfx.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/19/sfx.ogg
--------------------------------------------------------------------------------
/images/book/19/song.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/19/song.ogg
--------------------------------------------------------------------------------
/images/book/21/face.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/21/face.png
--------------------------------------------------------------------------------
/images/book/22/shake.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/22/shake.gif
--------------------------------------------------------------------------------
/images/book/23/box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/box.png
--------------------------------------------------------------------------------
/images/book/23/loss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/loss.png
--------------------------------------------------------------------------------
/images/book/23/wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/wall.png
--------------------------------------------------------------------------------
/images/book/24/map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/map.png
--------------------------------------------------------------------------------
/images/book/7/fruits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/7/fruits.png
--------------------------------------------------------------------------------
/images/book/7/shift.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/7/shift.png
--------------------------------------------------------------------------------
/images/book/7/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/7/table.png
--------------------------------------------------------------------------------
/images/book/9/scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/9/scope.png
--------------------------------------------------------------------------------
/images/book/12/origin1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/12/origin1.png
--------------------------------------------------------------------------------
/images/book/12/origin2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/12/origin2.png
--------------------------------------------------------------------------------
/images/book/14/bullet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/14/bullet.png
--------------------------------------------------------------------------------
/images/book/16/radian.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/radian.gif
--------------------------------------------------------------------------------
/images/book/17/almost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/almost.png
--------------------------------------------------------------------------------
/images/book/17/jump_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump_2.png
--------------------------------------------------------------------------------
/images/book/17/jump_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump_3.png
--------------------------------------------------------------------------------
/images/book/18/colors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/colors.png
--------------------------------------------------------------------------------
/images/book/18/houses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/houses.png
--------------------------------------------------------------------------------
/images/book/18/one_row.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/one_row.png
--------------------------------------------------------------------------------
/images/book/18/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/player.png
--------------------------------------------------------------------------------
/images/book/18/tileset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/tileset.png
--------------------------------------------------------------------------------
/images/book/20/error1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error1.png
--------------------------------------------------------------------------------
/images/book/20/error2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error2.png
--------------------------------------------------------------------------------
/images/book/20/error3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error3.png
--------------------------------------------------------------------------------
/images/book/20/error4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error4.png
--------------------------------------------------------------------------------
/images/book/20/error5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error5.png
--------------------------------------------------------------------------------
/images/book/20/error6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error6.png
--------------------------------------------------------------------------------
/images/book/20/error7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/error7.png
--------------------------------------------------------------------------------
/images/book/20/hendrik.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/20/hendrik.png
--------------------------------------------------------------------------------
/images/book/21/dollar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/21/dollar.png
--------------------------------------------------------------------------------
/images/book/23/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/player.png
--------------------------------------------------------------------------------
/images/book/24/falling.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/falling.gif
--------------------------------------------------------------------------------
/images/book/24/gravity.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/gravity.gif
--------------------------------------------------------------------------------
/images/book/24/jumping.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/jumping.gif
--------------------------------------------------------------------------------
/images/book/24/midair.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/midair.gif
--------------------------------------------------------------------------------
/images/book/24/through.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/through.gif
--------------------------------------------------------------------------------
/images/book/4/drawmode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/4/drawmode.png
--------------------------------------------------------------------------------
/images/book/11/blueprint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/11/blueprint.png
--------------------------------------------------------------------------------
/images/book/11/extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/11/extension.png
--------------------------------------------------------------------------------
/images/book/15/compress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/15/compress.png
--------------------------------------------------------------------------------
/images/book/16/arrow_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/arrow_off.png
--------------------------------------------------------------------------------
/images/book/16/sinecosine.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/sinecosine.gif
--------------------------------------------------------------------------------
/images/book/16/triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/triangle.png
--------------------------------------------------------------------------------
/images/book/17/bleeding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/bleeding.png
--------------------------------------------------------------------------------
/images/book/17/jump_help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/jump_help.png
--------------------------------------------------------------------------------
/images/book/18/2d_table.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/2d_table.gif
--------------------------------------------------------------------------------
/images/book/18/numbered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/numbered.png
--------------------------------------------------------------------------------
/images/book/24/platform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/24/platform.png
--------------------------------------------------------------------------------
/images/book/4/rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/4/rectangle.png
--------------------------------------------------------------------------------
/images/book/5/coordinates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/5/coordinates.png
--------------------------------------------------------------------------------
/images/book/5/rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/5/rectangle.png
--------------------------------------------------------------------------------
/images/book/1/download_love.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/1/download_love.png
--------------------------------------------------------------------------------
/images/book/1/install_brane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/1/install_brane.png
--------------------------------------------------------------------------------
/images/book/1/project_brane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/1/project_brane.png
--------------------------------------------------------------------------------
/images/book/13/rectangles1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/13/rectangles1.png
--------------------------------------------------------------------------------
/images/book/13/rectangles2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/13/rectangles2.png
--------------------------------------------------------------------------------
/images/book/13/rectangles3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/13/rectangles3.png
--------------------------------------------------------------------------------
/images/book/16/arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/arrow_right.png
--------------------------------------------------------------------------------
/images/book/16/pythagorean.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/pythagorean.png
--------------------------------------------------------------------------------
/images/book/16/sinecosine2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/sinecosine2.png
--------------------------------------------------------------------------------
/images/book/16/sinecosine3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/sinecosine3.png
--------------------------------------------------------------------------------
/images/book/16/sinecosine4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/sinecosine4.png
--------------------------------------------------------------------------------
/images/book/17/bleeding_fix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/bleeding_fix.png
--------------------------------------------------------------------------------
/images/book/17/rectangles1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/rectangles1.png
--------------------------------------------------------------------------------
/images/book/18/colors_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/colors_image.png
--------------------------------------------------------------------------------
/images/book/18/tile-move-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/tile-move-1.gif
--------------------------------------------------------------------------------
/images/book/18/tile-move-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/tile-move-2.gif
--------------------------------------------------------------------------------
/images/book/21/coin_grower.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/21/coin_grower.gif
--------------------------------------------------------------------------------
/images/book/21/table_remove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/21/table_remove.png
--------------------------------------------------------------------------------
/images/book/22/splitscreen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/22/splitscreen.gif
--------------------------------------------------------------------------------
/images/book/23/box_wall_bad.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/box_wall_bad.gif
--------------------------------------------------------------------------------
/images/book/1/download_brane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/1/download_brane.png
--------------------------------------------------------------------------------
/images/book/15/control_panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/15/control_panel.png
--------------------------------------------------------------------------------
/images/book/15/folder_options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/15/folder_options.png
--------------------------------------------------------------------------------
/images/book/15/personalization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/15/personalization.png
--------------------------------------------------------------------------------
/images/book/16/following_arrow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/following_arrow.gif
--------------------------------------------------------------------------------
/images/book/17/quad_position.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/quad_position.png
--------------------------------------------------------------------------------
/images/book/17/whatisgoingon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/17/whatisgoingon.png
--------------------------------------------------------------------------------
/images/book/18/multiple_rows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/18/multiple_rows.png
--------------------------------------------------------------------------------
/images/book/22/camera_center.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/22/camera_center.gif
--------------------------------------------------------------------------------
/images/book/23/box_wall_good.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/box_wall_good.gif
--------------------------------------------------------------------------------
/images/book/5/rectangle_move.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/5/rectangle_move.gif
--------------------------------------------------------------------------------
/images/book/6/rectangle_right.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/6/rectangle_right.gif
--------------------------------------------------------------------------------
/images/book/6/rectangle_stop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/6/rectangle_stop.gif
--------------------------------------------------------------------------------
/images/book/bonus/vscode/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/bonus/vscode/error.png
--------------------------------------------------------------------------------
/images/book/16/following_circle.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/following_circle.gif
--------------------------------------------------------------------------------
/images/book/21/circles_collision.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/21/circles_collision.png
--------------------------------------------------------------------------------
/images/book/23/collision_checks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/collision_checks.png
--------------------------------------------------------------------------------
/images/book/23/hor_ver_collision.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/hor_ver_collision.gif
--------------------------------------------------------------------------------
/images/book/4/example_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/4/example_rectangle.png
--------------------------------------------------------------------------------
/images/book/8/moving_rectangles.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/8/moving_rectangles.gif
--------------------------------------------------------------------------------
/images/book/21/table_remove_reverse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/21/table_remove_reverse.png
--------------------------------------------------------------------------------
/images/book/22/camera_center_moving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/22/camera_center_moving.gif
--------------------------------------------------------------------------------
/images/book/bonus/vscode/debugging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/bonus/vscode/debugging.png
--------------------------------------------------------------------------------
/images/book/bonus/vscode/lovepath.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/bonus/vscode/lovepath.gif
--------------------------------------------------------------------------------
/images/book/23/collision_to_previous.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/23/collision_to_previous.gif
--------------------------------------------------------------------------------
/images/book/11/moving_rectangle_circle.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/11/moving_rectangle_circle.gif
--------------------------------------------------------------------------------
/images/book/11/moving_rectangles_classes.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/11/moving_rectangles_classes.gif
--------------------------------------------------------------------------------
/images/book/16/following_circle_distance.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/following_circle_distance.gif
--------------------------------------------------------------------------------
/images/book/16/following_circle_distance_speed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sheepolution/how-to-love/HEAD/images/book/16/following_circle_distance_speed.gif
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 | # How to (make games with) LÖVE
3 | How to (make games with) LÖVE is a tutorial series made by me, Sheepolution. I decided to put it on Github so that others can contribute by fixing grammar-errors, code-errors or improvements on wording and explanation.
4 |
5 | The tutorial is hosted on [sheepolution.com/learn/book](http://www.sheepolution.com/learn/book/contents).
6 |
7 | [Parsedown](http://parsedown.org/tests/) is used to convert markdown to html.
8 |
9 |
10 | ## Planned chapters (random order)
11 |
12 | * Tweening
13 | * Terminology
14 | * Clean code
15 | * Physics
16 | * ECS
17 | * Networking
18 |
19 | If you have more ideas for chapters, you can open an issue about it.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sheepolution
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/book/contents.php:
--------------------------------------------------------------------------------
1 |
Table of Contents
2 |
81 | Bonus chapters
82 |
--------------------------------------------------------------------------------
/book/chapter15.md:
--------------------------------------------------------------------------------
1 |
2 | # Chapter 15 - Distributing your game
3 |
4 | ___
5 | *We use the code from the previous chapter*
6 | ___
7 |
8 | **Tip: Check out the [bonus chapter](bonus/vscode) for a more smooth way to build your game using makelove.**
9 | ___
10 |
11 | You made a game, and you want to share it with others. You could make them install LÖVE on their computer, but that is not necessary.
12 |
13 | First, we need to change the title and icon. For that we will use a config file.
14 |
15 | Create a new file called conf.lua, and put in the following code
16 |
17 | ```lua
18 | function love.conf(t)
19 | t.window.title = "Panda Shooter!"
20 | t.window.icon = "panda.png"
21 | end
22 | ```
23 |
24 | Save the file. Now when you run the game you'll see the game has the title "Panda Shooter!", and that the icon is the panda.
25 |
26 | This is what the config file is for. LÖVE loads `conf.lua` before it starts the game and applies the configurations. For a full list of options check out the [wiki](https://love2d.org/wiki/Config_Files).
27 |
28 | Now that our game has the correct title and icon, let's turn it into an executable.
29 |
30 | First we need to package our game in a zip file. Go to the folder of your game, select all the files. Now right click, go to *Send to* and click on *Compressed (zipped) folder*. The filename is not important, but we need to change the extension to `.love` (by default `.zip`).
31 |
32 | *Note: This part is Windows only. Go [here](https://www.love2d.org/wiki/Game_Distribution) to get info on building your game for other platforms.*
33 |
34 | 
35 |
36 | **If you can't see file extensions**
37 |
38 | Press ***Windows + pause/break***. In the upper-left corner of the new opened window click on ***Control Panel***. Now go to ***Appearance and Personalization***.
39 |
40 | 
41 |
42 | Click on ***File Explorer options***.
43 |
44 | 
45 |
46 | A new window opens. Click on the tab ***View***. In ***Advanced options***, make sure that ***Hide extension for known filetypes*** is unchecked.
47 |
48 | 
49 |
50 | I wrote a bat file that packages the game for you. Download [this zip file](https://drive.google.com/file/d/1xX49nDCI0UxjnwY3-h0f-kpdmHVmNqvz/view?usp=sharing), and unzip all the files in a folder.
51 |
52 | Now move your `.love` file on top of `build.bat`. This creates a `.zip` file in the same folder. This is the file that you will want to share with people. They have to extract all the files in a folder and open the `.exe` file.
53 |
54 | Now you need to find a place to share your game. Check out [itch.io](https://itch.io/).
55 |
56 | For more information on building your game, check out the [LÖVE wiki page](https://www.love2d.org/wiki/Game_Distribution) for it. It also tells you how to build your game for other platforms.
57 |
58 | ___
59 |
60 | ## Summary
61 | With [conf.lua](https://love2d.org/wiki/Config_Files) you can configure things about your game like title and icon. Select all the files in the folder of your game, put them in a zip. Change the file's extension from `.zip` to `.love`. Download [this zip file](https://drive.google.com/file/d/1xX49nDCI0UxjnwY3-h0f-kpdmHVmNqvz/view?usp=sharing), and unzip all the files in a folder. Move your `.love` on top of `build.bat` to create a `.zip`. People will have to unzip all the files in a folder and open the `.exe` to play your game.
62 |
--------------------------------------------------------------------------------
/book/chapter19.md:
--------------------------------------------------------------------------------
1 | # Chapter 19 - Audio
2 |
3 | We're going to add audio. This chapter is very short. That's because just like with a lot of things, the LÖVE API makes it very easy for us to add audio to the game.
4 |
5 | Download these two files that we will be using in this tutorial:
6 |
7 | [*sfx.ogg*](/images/book/19/sfx.ogg) and [*song.ogg*](/images/book/19/song.ogg)
8 |
9 | Let's start with the song and make it so that it loops endlessly.
10 | First we need to create the audio. We call this a *source* (the source of the audio). We can create the source with `love.audio.newSource(path)`. This function takes two arguments. First is the path to the file, second is what type of source it is. This can be either `"static"` or `"stream"`. Let me quote the LÖVE wiki: "A good rule of thumb is to use stream for music files and static for all short sound effects. Basically, you want to avoid loading large files at once."
11 |
12 | So in our case we want to use `"stream"`, since it's a song.
13 |
14 | ```lua
15 | function love.load()
16 | song = love.audio.newSource("path/to/song.ogg", "stream")
17 | end
18 | ```
19 |
20 | Next we want to play it. There are two ways to do it.
21 |
22 | ```lua
23 | function love.load()
24 | song = love.audio.newSource("path/to/song.ogg", "stream")
25 | -- method 1
26 | love.audio.play(song)
27 | --method 2
28 | song:play()
29 | end
30 | ```
31 |
32 | There isn't really a difference between the two, and if there is it's not a difference that you will notice. So use whatever you prefer. I prefer the second method.
33 |
34 | When you run the game the song should now play. But it doesn't loop. How can we fix this? I can tell you, but you should learn how to find these kind of things yourself. First, let's go to wiki page of the [Source](http://love2d.org/wiki/Source) object. Now we need to look for something that might help us loop the audio. Perhaps press Ctrl + F to search on the page, and then type "loop".
35 |
36 | Source:setLooping Sets whether the Source should loop.
37 |
38 | Got it!
39 |
40 | ```lua
41 | function love.load()
42 | song = love.audio.newSource("path/to/song.ogg", "stream")
43 | song:setLooping(true)
44 | song:play()
45 | end
46 | ```
47 |
48 | Okay nice, now we got looping background music. Next we want to add a sound effect. Let's make it so that the sound effect is played every time you press Space. We start with creating the new source.
49 |
50 | ```lua
51 | function love.load()
52 | song = love.audio.newSource("path/to/song.ogg", "stream")
53 | song:setLooping(true)
54 | song:play()
55 |
56 | -- sfx is short for 'sound effect', or at least I use it like that.
57 | sfx = love.audio.newSource("sfx.ogg", "static")
58 | end
59 | ```
60 |
61 | Next we add the keypressed callback, and make the sound effect play every time we press "space".
62 |
63 | ```lua
64 | function love.keypressed(key)
65 | if key == "space" then
66 | sfx:play()
67 | end
68 | end
69 | ```
70 |
71 | And we're done. Like I said, there isn't much to say about audio, and everything you want to learn you can easily look up yourself in the API documentation. For example, how to [set the volume](http://love2d.org/wiki/Source:setVolume), how to make the source [pause](http://love2d.org/wiki/Source:pause) or how to get the source's [position](http://love2d.org/wiki/Source:tell).
72 |
73 | ___
74 |
75 | ## Summary
76 | The LÖVE API makes it very easy to add audio. We call an audio object a Source. Often we can figure out how to do something by looking through the API documentation.
--------------------------------------------------------------------------------
/book/chapter10.md:
--------------------------------------------------------------------------------
1 | # Chapter 10 - Libraries
2 |
3 | A library is code that everyone can use to add certain functionality to their project.
4 |
5 | Let's try out a library. We're going to use *tick* by *rxi*. You can find the library on [GitHub](https://github.com/rxi/tick).
6 |
7 | Click on `tick.lua` and then on Raw, and [copy the code](https://raw.githubusercontent.com/rxi/tick/master/tick.lua).
8 |
9 | Go to your text editor, create a new file called `tick.lua` and paste the code.
10 |
11 | Now we have to follow the instructions on the GitHub page. First we have to load it with `require`.
12 |
13 | ```lua
14 | function love.load()
15 | tick = require "tick"
16 | end
17 | ```
18 |
19 | Notice how `require` doesn't have parantheses (). This is because when you only pass 1 argument, you don't have to use them. Now I recommend you still do use them for any other function, but with `require` it's common to leave them out. But in the end, it doesn't even matter.
20 |
21 | Next we have to put `tick.update(dt)` in our updater.
22 |
23 | ```lua
24 | function love.update(dt)
25 | tick.update(dt)
26 | end
27 | ```
28 |
29 | And now we're ready to start using the library. Let's make it so that a rectangle will be drawn after 2 seconds.
30 |
31 | ```lua
32 | function love.load()
33 | tick = require "tick"
34 |
35 | --Create a boolean
36 | drawRectangle = false
37 |
38 | --The first argument is a function
39 | --The second argument is the time it takes to call the function
40 | tick.delay(function () drawRectangle = true end , 2)
41 | end
42 |
43 |
44 | function love.draw()
45 | --if drawRectangle is true then draw a rectangle
46 | if drawRectangle then
47 | love.graphics.rectangle("fill", 100, 100, 300, 200)
48 | end
49 | end
50 |
51 |
52 | ```
53 | Did we just pass a function as an argument? Sure, why not? A function is a type of variable after all.
54 |
55 | When you run the game you can see that with this library we can put a delay on calling functions. And like that there are tons of libraries with all kinds of functionality.
56 |
57 | Don't feel guilty for using a library. Why reinvent the wheel? That is, unless you are interested in learning it. I personally use about 10 libraries in my projects. They provide functionality that I don't understand how to make myself, and I'm simply not interested in learning it.
58 |
59 | Libraries aren't magic. It's all Lua code that you and I could've written as well (in case we have the knowledge of course). We'll create a library in a future chapter so that we have a better understanding of how they work.
60 |
61 | ___
62 |
63 | ## Standard libraries
64 |
65 | Lua has built-in libraries. These are called *Standard Libraries*. They are the functions that are built into Lua. `print`, for example, is part of the standard library. And so is `table.insert` and `table.remove`.
66 |
67 | One important standard library we haven't looked at yet is the *math library*. It provides math functions, which can be very useful when making a game.
68 |
69 | For example, `math.random` gives us a random number. Let's use it to place a rectangle on a random position whenever you press the spacebar.
70 |
71 | ```lua
72 | function love.load()
73 | x = 30
74 | y = 50
75 | end
76 |
77 |
78 | function love.draw()
79 | love.graphics.rectangle("line", x, y, 100, 100)
80 | end
81 |
82 |
83 | function love.keypressed(key)
84 | --If space is pressed then..
85 | if key == "space" then
86 | --x and y become a random number between 100 and 500
87 | x = math.random(100, 500)
88 | y = math.random(100, 500)
89 | end
90 | end
91 | ```
92 |
93 | Now that we understand what libraries are, we can start using a class library.
94 |
95 | ___
96 |
97 | ## Summary
98 | Libraries are code that gives us functionality. Anyone can make a library. Lua also has built-in libraries which we call the Standard Libraries.
99 |
--------------------------------------------------------------------------------
/book/chapter0.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | ___
4 |
5 | *Are you so excited about learning how to make games that you can't be bothered reading my boring introduction? I understand, go ahead and skip this if you want. Good luck!*
6 |
7 | ___
8 |
9 | Welcome to How to LÖVE. The tutorial that teaches you how to program games with LÖVE (aka Love2D). Written by me, Sheepolution. It's the tutorial that I wish was available when I started learning how to program.
10 |
11 | This tutorial is for people who want to learn how to program. You don't necessarily need to learn how to program to make a game. If you want to make a single simple game and not touch game development after that again, I recommend using a tool like Construct 2 or Game Maker.
12 |
13 | In this tutorial we will be using LÖVE. The reason for this is because LÖVE is very beginner friendly in terms of programming games, but provides the functionality to make professional 2D games. This makes it a great tool for both beginners and experienced users. [Here's a list](https://store.steampowered.com/curator/44895761-Made-With-L%25C3%2596VE/) of all the games on Steam that were made with LÖVE.
14 |
15 | Unlike Game Maker and Unity, LÖVE is a framework and not an engine. You will have to do everything with code. There is no built in level editor or anything. The plus side to this is that you will understand exactly what is going on and how it works, because you wrote it yourself. Your knowledge is also easier to transfer to other engines and frameworks. Because while every engine works differently, code always works the same.
16 |
17 | This tutorial is written for people without any programming experience. If you do have experience with programming, I recommend you at least read the summary of every chapter, and when it confuses you, you read the full chapter.
18 |
19 | If you *don't* have any experience in programming, I hope this tutorial will teach you what you want to know. You probably want to start making a game right away, but I'll have to ask of you to be patient. There are a lot of concepts surrounding programming that you will need to understand before you can start making a game. And it can take a while before you're fully grasping these concepts. There is a good chance that you will be reading this book and not fully understanding what you're reading. In that case my advice is to ask questions (see below) and perhaps to continue with the book, pretending that you know what is going on. Maybe at chapter 12 you're finally understanding what was explained in chapter 8. In any case, don't feel bad or dumb if you don't understand everything right away. Take your time.
20 |
21 | When you feel like it, I encourage you to "skip ahead". With that I mean try out things that the book hasn't taught you yet. Play around with what you know already and look up things you want to know. This makes for some great practice and in the end that is what you need. You can read about painting all you want, but to learn you'll have to paint yourself. Same goes for programming.
22 |
23 | Some chapters will use code from the previous chapter. In that case I will point it out at the top of the chapter.
24 |
25 | I'm not done writing this tutorial, meaning there are still new chapters to be released, but I have no concrete plans for when that will happen.
26 |
27 | If you have any questions, improvements, or anything else to say, feel free to leave a comment behind, and I'll do my best to reply as soon as possible. You can also ask for help in the [LÖVE Discord server](https://discord.gg/MHtXaxQ).
28 |
29 | ___
30 |
31 | ## Summary
32 |
33 | This is a programming tutorial that uses LÖVE because it's very easy for beginners.
34 |
35 | ___
36 |
37 | ## What will you be learning?
38 |
39 | Here are some gifs to give you an impression of what you're going to learn throughout this tutorial.
40 |
41 | 
42 |
43 | 
44 |
45 |  
46 |
47 | 
48 |
49 | 
50 |
51 | 
52 |
53 | 
54 |
--------------------------------------------------------------------------------
/book/chapter3.md:
--------------------------------------------------------------------------------
1 | # Chapter 3 - Functions
2 |
3 | With functions, we can store pieces of code. This allows us to execute this code whenever we want. Functions are also known as `methods`.
4 |
5 | There are 2 ways to create a function:
6 |
7 | ```lua
8 | example = function ()
9 | print("Hello World!")
10 | end
11 | ```
12 |
13 | and the more common way:
14 |
15 | ```lua
16 | function example()
17 | print("Hello World!")
18 | end
19 | ```
20 |
21 | You start by writing the keyword `function`, followed by the name of the function. A function is a type of variable, so the same rules apply as when you name a variable. This function's name is `example`. After the name we put parentheses `()`. Now we can start typing the code we want to put inside our function. In this case, we put in `print("Hello World!")` When you're done you close the function with an `end`.
22 |
23 | Note that when you run the code, you'll see no "Hello World!" in your console, this is because we still have to execute the function. We execute a function like this:
24 | ```lua
25 | example()
26 | --Output: "Hello World!"
27 | ```
28 | You simply type the function's name, followed by parentheses. This is what we call a *function-call*.
29 |
30 | ___
31 |
32 | ## Parameters
33 |
34 | Take a look at this code:
35 | ```lua
36 | function sayNumber(num)
37 | print("I like the number " .. num)
38 | end
39 |
40 | sayNumber(15)
41 | sayNumber(2)
42 | sayNumber(44)
43 | sayNumber(100)
44 | print(num)
45 | --Output:
46 | --"I like the number 15"
47 | --"I like the number 2"
48 | --"I like the number 44"
49 | --"I like the number 100"
50 | --nil
51 | ```
52 |
53 |
54 | Inside the parentheses of the function we can put what we call *parameters*. Parameters are temporary variables that only exist inside the function. In this case we place the parameter `num`. And now we can use `num` like any other variable.
55 |
56 | We execute our function multiple times, each time with a different number. And thus each time we print the same sentence, but with a different number. The number we put inside the parentheses is what we call an argument. So in the first function-call, we *pass* the *argument* 15 to the *parameter* `num`.
57 |
58 | At the end of our code we print `num`, outside of our function. This gives us `nil`. This means that num has no value. It's not a number, or string, or function. It's nothing. Because like I said before, parameters are variables that are only available inside the function.
59 |
60 | ___
61 |
62 | ## Return
63 | Functions can return a value, which we can store inside a variable, for example. You can return a value by using the `return` keyword.
64 |
65 | ```lua
66 | function giveMeFive()
67 | return 5
68 | end
69 |
70 | a = giveMeFive()
71 | print(a)
72 | --Output: 5
73 | ```
74 |
75 | `a` becomes the value that giveMeFive *returns*.
76 |
77 | Another example:
78 |
79 | ```lua
80 | -- Multiple parameters and arguments are separated by a comma
81 | function sum(a, b)
82 | return a + b
83 | end
84 |
85 | print(sum(200, 95))
86 | --Output:
87 | --295
88 | ```
89 | Our function `sum` *returns* the sum of `a` and `b`. We don't necessarily need to put the value our function returns in a variable first. We can directly print the value.
90 |
91 | ___
92 |
93 | ## Usage
94 | Often you want to execute certain code in multiple locations. Instead of copying that code each time you want to use it, we can simply add a function call. And if we want to change the behaviour of this function, we only need to change it in one location, which is the function itself. This way we avoid repeating code. [Don't repeat yourself](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), it's one of the most important programming principles.
95 |
96 | ___
97 |
98 | ## Summary
99 | Functions can store code that we can execute at any time. We call a function by writing its name followed by parentheses. We can put values inside these parentheses. These values are passed to the function's parameters, which are temporary variables that only exist within the function. Functions can also return a value. Functions remove repetition and that is a good thing.
100 |
--------------------------------------------------------------------------------
/book/chapter2.md:
--------------------------------------------------------------------------------
1 | # Chapter 2 - Variables
2 | With programming we can do arithmetics.
3 |
4 | What is 3 + 4?
5 |
6 | *It's 7!*
7 |
8 | Okay well let's test that. We can use `print` to make the number appear in our output console.
9 |
10 | ```lua
11 | print(3 + 4)
12 | --Output: 7
13 | ```
14 |
15 | Run your code (meaning press F6 and then close the window to show the output) and your console should say `7`.
16 |
17 | Cool! Now what is a + b?
18 |
19 | *Uhm...*
20 |
21 | Well it could be anything. That's because "a" and "b" don't have a value. Let's change that.
22 |
23 | ```lua
24 | a = 5
25 | b = 3
26 | ```
27 |
28 | Let's take another look, what is a + b? What we're really asking is "What is the value of a + the value of b?". In other words, what is 5 + 3? Which is 8.
29 |
30 | To prove that a + b = 8, we're going to print it.
31 |
32 | ```lua
33 | a = 5
34 | b = 3
35 | print(a + b)
36 | --Output: 8
37 | ```
38 |
39 | Run your code again.
40 |
41 | Here `a` and `b` are what we call *variables*. A variable is a word in which you can store a value. The number 3 is always 3, and 7 is always 7, but a variable can be anything you want it to be. Hence the name variable.
42 |
43 | The word in which you store a value can be almost anything.
44 | ```lua
45 | sheep = 3
46 | test = 20
47 | PANTS = 1040
48 | asdfghjkl = 42
49 | ```
50 |
51 | Variables are *case-sensitive*. That means that when you have the same word, but with different casing, it's not treated as the same. For example
52 | ```lua
53 | sheep = 3
54 | SHEEP = 10
55 | sHeEp = 200
56 | ```
57 | are three different variables, with each their own value.
58 |
59 | You can do more than just summing up numbers.
60 | ```lua
61 | a = 20 - 10 --Subtraction
62 | b = 20 * 10 --Multiplication
63 | c = 20 / 10 --Division
64 | d = 20 ^ 10 --Exponentiation
65 | ```
66 | For numbers with decimals we use a dot.
67 |
68 | ```lua
69 | a = 10.4
70 | b = 2.63
71 | c = 0.1
72 | pi = 3.141592
73 | ```
74 |
75 | Take a look at the following code:
76 |
77 | ```lua
78 | X = 5
79 | Y = 3
80 | Z = X + Y
81 | ```
82 |
83 | First we say `X = 5`. When we give a variable a value, we call that an *assignment*. We *assign* 5 to `X`, and 3 to `Y`. Next we assign `X + Y` to `Z`. So now `Z` equals 8. Remember that you can always check the value of a variable with `print`. If we were to change the value of `X` or `Y` after `Z = X + Y`, it would not affect `Z`. It would still be 8.
84 |
85 | ```lua
86 | X = 5
87 | Y = 3
88 | Z = X + Y
89 | X = 2
90 | Y = 40
91 | print(Z)
92 | --Output: 8
93 | ```
94 | This is because to the computer `Z` is not `X + Y`, it's simply 8.
95 |
96 | ___
97 | ## Strings
98 | A variable can also store text.
99 | ```lua
100 | text = "Hello World!"
101 | ```
102 | This is what we call a *string*. Because it's a string of characters.
103 |
104 | We can connect strings by using two dots (..)
105 | ```lua
106 | name = "Daniel"
107 | age = "25"
108 | text = "Hello, my name is " .. name .. ", and I'm " .. age .. " years old."
109 | print(text)
110 | --Output: "Hello, my name is Daniel, and I'm 25 years old."
111 | ```
112 | ___
113 |
114 | ## Variable naming rules
115 | There are a few rules when naming a variable. First of all, your variable may have a number in it, but not at the start.
116 |
117 | ```lua
118 | test8 --Good
119 | te8st --Good
120 | 8test --Bad, error!
121 | ```
122 |
123 | Your variable name also can't include any special characters like @#$%^&*.
124 |
125 | And finally, your variable name can't be a keyword. A keyword is a word that the programming language uses. Here's a list of keywords:
126 |
127 | ```nil
128 | and break do else elseif
129 | end false for function if
130 | in local nil not or
131 | repeat return then true until while
132 | ```
133 |
134 | ___
135 |
136 | ## Usage
137 |
138 | Variables can be used to keep track of things. For example, we can have the variable `coins`, and every time we pick up a coin we can do `coins = coins + 1`.
139 |
140 | ___
141 |
142 | ## Summary
143 | Variables are words in which we can store a value like a number or text. You can name them whatever you want, with a few exceptions. Variables are case-sensitive.
144 |
--------------------------------------------------------------------------------
/book/chapter5.md:
--------------------------------------------------------------------------------
1 | # Chapter 5 - Moving a rectangle
2 |
3 | Now we can start with what I like to call "The fun part". We're going to make stuff move!
4 |
5 | Let's start with the 3 main callbacks.
6 |
7 | ```lua
8 | function love.load()
9 |
10 | end
11 |
12 |
13 | function love.update()
14 |
15 |
16 | end
17 |
18 |
19 | function love.draw()
20 |
21 | end
22 | ```
23 |
24 | Next, we draw a rectangle.
25 |
26 | ```lua
27 | function love.draw()
28 | love.graphics.rectangle("line", 100, 50, 200, 150)
29 | end
30 | ```
31 |
32 | 
33 |
34 | The second and third argument of this function are the x and y position.
35 |
36 | x means "horizontal position on the screen". 0 is the left side of the screen.
37 |
38 | y means "vertical position on the screen". 0 is the top side of the screen.
39 |
40 | 
41 |
42 | Now we want to make the rectangle move. It's time to start thinking like a programmer. What exactly needs to happen in order for the rectangle to move to the right? The x-position needs to go up. 100, 101, 102, 103, 104, etc. But we can't change 100 to 101. 100 is simply 100. We need to have something that can change in any number we want it to be. That's right, a **variable**!
43 |
44 | In love.load, create a new variable called `x`, and replace the 100 in `love.graphics.rectangle` with `x`.
45 |
46 | ```lua
47 | function love.load()
48 | x = 100
49 | end
50 |
51 | function love.draw()
52 | love.graphics.rectangle("line", x, 50, 200, 150)
53 | end
54 | ```
55 |
56 | So now the x-position of our rectangle is the value of `x`.
57 |
58 | Note that the variable name `x` is just a name. We could've named it `icecream` or `unicorn` or whatever. Functions don't care about variable names, it only cares about its value.
59 |
60 | Now we want to make the rectangle move. We do this in love.update. Every update we want to increase `x` by 5. In other words, `x` needs to be the value of `x` + 5. And that's exactly how we write it.
61 |
62 | ```lua
63 | function love.update()
64 | x = x + 5
65 | end
66 | ```
67 |
68 | So now when `x` equals 100, it will change `x` into 100 + 5. Then next update `x` is 105 and `x` will change into 105 + 5, etc.
69 |
70 | Run the game. The rectangle should now be moving.
71 |
72 | 
73 |
74 | ___
75 |
76 | ## Delta time
77 |
78 | We got a moving rectangle, but there's one small problem. If you were to run the game on a different computer, the rectangle might move with a different speed. This is because not all computers update at the same rate, and that can cause problems.
79 |
80 | For example, let's say that Computer A runs with 100 fps (frames per second), and Computer B runs with 200 fps.
81 |
82 | 100 x 5 = 500
83 |
84 | 200 x 5 = 1000
85 |
86 | So in 1 second, `x` has increased with 500 on computer A, while on computer B `x` has increased with 1000.
87 |
88 | Luckily, there's a solution for this: delta time.
89 |
90 | When LÖVE calls love.update, it passes an argument. Add the parameter dt (short for delta time) in the love.update, and let's print it.
91 |
92 | ```lua
93 | function love.update(dt)
94 | print(dt)
95 | x = x + 5
96 | end
97 | ```
98 |
99 | Delta time is the time that has passed between the previous and the current update. So on computer A, which runs with 100 fps, delta time on average would be 1 / 100, which is 0.01.
100 |
101 | On computer B, delta time would be 1 / 200, which is 0.005.
102 |
103 | So in 1 second, computer A updates 100 times, and increases `x` by `5 * 0.01`, and computer B updates 200 times and increases `x` by `5 * 0.005`.
104 |
105 | `100 * 5 * 0.01 = 5`
106 |
107 | `200 * 5 * 0.005 = 5`
108 |
109 | By using delta time our rectangle will move with the same speed on all computers.
110 |
111 | ```lua
112 | function love.update(dt)
113 | x = x + 5 * dt
114 | ```
115 |
116 | Now our rectangle moves 5 pixels per second, on all computers. Change 5 to 100 to make it go faster.
117 |
118 | ___
119 |
120 | ## Summary
121 | We use a variable that we increase on each update to make the rectangle move. When increasing we multiply the added value by delta time. Delta time is the time between the previous and current update. Using delta time makes sure that our rectangle moves with the same speed on all computers.
122 |
--------------------------------------------------------------------------------
/book/chapter8.md:
--------------------------------------------------------------------------------
1 | # Chapter 8 - Objects
2 | In the previous chapter we used tables as numbered lists, but we can also store values in a different way: With strings.
3 |
4 | ```lua
5 | function love.load()
6 | --rect is short for rectangle
7 | rect = {}
8 | rect["width"] = 100
9 | end
10 | ```
11 |
12 | `"width"` in this case is what we call a *key* or a *property*. So the table `rectangle` now has the property `"width"` with a value of 100. We don't need to use strings every time we want to create property. A dot (.) is the shorthand for `table_name["property_name"]`.
13 |
14 | ```lua
15 | function love.load()
16 | rect = {}
17 | -- These two are the same
18 | rect["width"] = 100
19 | rect.width = 100
20 | end
21 | ```
22 |
23 | Let's add some more properties.
24 |
25 | ```lua
26 | function love.load()
27 | rect = {}
28 | rect.x = 100
29 | rect.y = 100
30 | rect.width = 70
31 | rect.height = 90
32 | end
33 | ```
34 |
35 | Now that we have our properties we can start drawing the rectangle.
36 |
37 | ```lua
38 | function love.draw()
39 | love.graphics.rectangle("line", rect.x, rect.y, rect.width, rect.height)
40 | end
41 | ```
42 |
43 | And let's make it move!
44 |
45 | ```lua
46 | function love.load()
47 | rect = {}
48 | rect.x = 100
49 | rect.y = 100
50 | rect.width = 70
51 | rect.height = 90
52 |
53 | --Add a speed property
54 | rect.speed = 100
55 | end
56 |
57 | function love.update(dt)
58 | -- Increase the value of x. Don't forget to use delta time.
59 | rect.x = rect.x + rect.speed * dt
60 | end
61 | ```
62 |
63 | Now we have a moving rectangle again, but to show the power of tables I want to create multiple moving rectangles. For this we're going to use a table as a list. We'll make a list of rectangles. Move the code inside the `love.load` to a new function, and create a new table in `love.load`.
64 |
65 | ```lua
66 | function love.load()
67 | -- Remember: camelCasing!
68 | listOfRectangles = {}
69 | end
70 |
71 | function createRect()
72 | rect = {}
73 | rect.x = 100
74 | rect.y = 100
75 | rect.width = 70
76 | rect.height = 90
77 | rect.speed = 100
78 |
79 | -- Put the new rectangle in the list
80 | table.insert(listOfRectangles, rect)
81 | end
82 | ```
83 |
84 | So now every time we call `createRect`, a new rectangle object will be added to our list. That's right, a table filled with tables. Let's make it so that whenever we press space, we call `createRect`. We can do this with the callback `love.keypressed`.
85 |
86 | ```lua
87 | function love.keypressed(key)
88 | -- Remember, 2 equal signs (==) for comparing!
89 | if key == "space" then
90 | createRect()
91 | end
92 | end
93 | ```
94 |
95 | Whenever we press a key, LÖVE will call love.keypressed, and pass the pressed key as argument. If that key is `"space"`, it will call `createRect`.
96 |
97 | Last thing to do is to change our update and draw function. We have to iterate through our list of rectangles.
98 |
99 | ```
100 | function love.update(dt)
101 | for i,v in ipairs(listOfRectangles) do
102 | v.x = v.x + v.speed * dt
103 | end
104 | end
105 |
106 | function love.draw(dt)
107 | for i,v in ipairs(listOfRectangles) do
108 | love.graphics.rectangle("line", v.x, v.y, v.width, v.height)
109 | end
110 | end
111 | ```
112 |
113 | And now when you run the game, a moving rectangle should appear every time you press space.
114 |
115 | 
116 |
117 | ___
118 |
119 | ## One more time?
120 | That was a lot of code in a rather short tutorial. I can imagine it can be quite confusing so let's go through the whole code one more time:
121 |
122 | In `love.load` we created a table called `listOfRectangles`.
123 |
124 | When we press space, LÖVE calls `love.keypressed`, and inside that function we check if the pressed key is `"space"`. If so, we call the function `createRect`.
125 |
126 | In `createRect` we create a new table. We give this table properties, like `x` and `y`, and we store this new table inside our list `listOfRectangles`.
127 |
128 | In `love.update` and `love.draw` we iterate through this list of rectangles, to update and draw each rectangle.
129 |
130 | ___
131 |
132 | ## Functions
133 |
134 | An object can also have functions. You create a function for an object like this:
135 |
136 | ```lua
137 | tableName.functionName = function ()
138 |
139 | end
140 |
141 | -- Or the more common way
142 | function tableName.functionName()
143 |
144 | end
145 | ```
146 |
147 | ___
148 |
149 | ## Summary
150 | We can store values in tables not only with numbers but also with strings. We call these type of tables *objects*. Having objects saves us from creating a lot of variables.
151 |
--------------------------------------------------------------------------------
/book/chapter1.md:
--------------------------------------------------------------------------------
1 | # Chapter 1 - Installation
2 |
3 | ___
4 |
5 | *Chapter 2 and 3 can be done without any installation. You can use [repl.it](https://repl.it/languages/lua) as an alternative if you don't feel like installing software right away. But be sure to read the **A few more things** paragraph at the bottom.*
6 |
7 | ___
8 |
9 | ## LÖVE
10 |
11 | Go to [love2d.org](https://www.love2d.org/).
12 |
13 | You should download either the 32-bit or the 64-bit **installer**. This depends on your system type. If you don't know your system type, just go with 32-bit.
14 |
15 | 
16 |
17 | Open the installer. Click on *Next*. Click on *I agree*. Now you can decide where you install LÖVE. It doesn't matter where you install LÖVE, but make sure you remember the location because we need it in a moment. This folder will be referred to as the *Installation Folder*.
18 |
19 | My installation folder will be `C:/Program Files/LOVE`.
20 |
21 | Click on *Next*. Click on *Install*.
22 |
23 | When LÖVE is done installing, click on *Finish*.
24 |
25 | ___
26 |
27 | ## Code editor
28 |
29 | Now we need to install a text editor. We're going to use ZeroBrane Studio in this tutorial.
30 |
31 | If you use **Visual Studio Code**, check out the [extra chapter](bonus/vscode) to learn how to run LÖVE in Visual Studio Code.
32 |
33 | Go to [studio.zerobrane.com](https://studio.zerobrane.com/), and click on "Download".
34 |
35 | 
36 |
37 | Here you get the option to donate to ZeroBrane Studio. If you don't want to donate click on ["Take me to the download page this time"](https://studio.zerobrane.com/download?not-this-time),
38 |
39 | Open the installer, and install ZeroBrane Studio in your preferred folder.
40 |
41 | 
42 |
43 | When ZeroBrane Studio is done installing, open it.
44 |
45 | Now we need to make a Project Folder. Open you file explorer and create a folder wherever you like, and name it whatever you want. In ZeroBrane Studio, click on the "Select Project Folder" icon, and select the folder you just created.
46 |
47 | 
48 |
49 |
50 | In ZeroBrane Studio, create a new file. `File` -> `New`, or use the shortcut `Ctrl + N`.
51 |
52 | Inside this file, write the following code:
53 | ```lua
54 | function love.draw()
55 | love.graphics.print("Hello World!", 100, 100)
56 | end
57 | ```
58 |
59 | Go to `File` -> `Save`, or use the shortcut `Ctrl + S`. Save the file as `main.lua`.
60 |
61 | Go to `Project` -> `Lua Interpreter` and select `LÖVE`.
62 |
63 | Now when you press the F6, a window should open with the text "Hello World!". Congratulations, you're ready to start learning LÖVE. Whenever I tell you to *run the game* or *run the code* I'm telling you to press F6 to execute LÖVE.
64 |
65 | In case nothing happens and the following text shows up: *Can't find love2d executable in any of the following folders*, you installed LÖVE somewhere where ZeroBrane Studio can't find it. Go to `Edit` -> `Preferrences` -> `Settings: User`. Put in the following:
66 |
67 | ```lua
68 | path.love2d = 'C:/path/to/love.exe'
69 | ```
70 |
71 | And replace `'C:/path/to/love.exe'` with the path to where you installed LÖVE. Make sure to use frontslashes (/).
72 |
73 | ___
74 |
75 | ## A few more things
76 |
77 | Did you copy/paste the example code? I encourage you to type the code I show you yourself. That might seem like a lot of extra work, but by doing so it will help you memorize everything a lot better.
78 |
79 | The one thing you don't need to type yourself are comments.
80 |
81 | ```lua
82 | -- This line is a comment. This is not code.
83 | -- The next line is code:
84 |
85 | print(123)
86 |
87 | --Output: 123
88 | ```
89 |
90 | Every line starting with 2 minus-signs (--) is a *comment*. The computer ignores it, meaning we can type anything we want without getting an error. I can use comments to explain certain code better. When typing over the code you don't have to copy the comments.
91 |
92 | With `print` we can send information to our *Output console*. This is the box at the bottom of our editor. **When you close the game**, it should say the text "123". I add the text `--Output:` to show you the expected output. This is not to be confused with `love.graphics.print`.
93 |
94 | If you put the following code at the top of your `main.lua` you will see the prints right away. How this works is not important, basically you turn off that the program should wait to be closed before showing the prints.
95 |
96 | ```lua
97 | io.stdout:setvbuf("no")
98 | ```
99 |
100 | ___
101 |
102 | ## Alternative text editors
103 |
104 | * [Visual Studio Code](https://code.visualstudio.com/)
105 | * [Sublime Text](https://love2d.org/wiki/Sublime_Text)
106 | * [Atom](https://love2d.org/wiki/Atom)
--------------------------------------------------------------------------------
/book/chapter4.md:
--------------------------------------------------------------------------------
1 | # Chapter 4 - LÖVE
2 |
3 | ## What is LÖVE?
4 | LÖVE is what we call a *framework*. Simply said: It's a tool that makes programming games easier.
5 |
6 | LÖVE is made with *C++* and *OpenGL*, which are both considered to be very difficult. The source code of LÖVE is very complex. But all this complex code makes creating a game a lot easier for us.
7 |
8 | For example, with `love.graphics.ellipse()`, we can draw an ellipse. This is the source code behind it:
9 |
10 | ```cpp
11 | void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points)
12 | {
13 | float two_pi = static_cast(LOVE_M_PI * 2);
14 | if (points <= 0) points = 1;
15 | float angle_shift = (two_pi / points);
16 | float phi = .0f;
17 |
18 | float *coords = new float[2 * (points + 1)];
19 | for (int i = 0; i < points; ++i, phi += angle_shift)
20 | {
21 | coords[2*i+0] = x + a * cosf(phi);
22 | coords[2*i+1] = y + b * sinf(phi);
23 | }
24 |
25 | coords[2*points+0] = coords[0];
26 | coords[2*points+1] = coords[1];
27 |
28 | polygon(mode, coords, (points + 1) * 2);
29 |
30 | delete[] coords;
31 | }
32 | ```
33 |
34 | You might not understand this code above at all (I barely do so myself), and that's exactly why we use LÖVE. It takes care of the hard parts of programming a game, and leaves the fun part for us.
35 |
36 | ___
37 |
38 | ## Lua
39 |
40 | Lua is the programming language that LÖVE uses. Lua is a programming language on its own, and is not made by or for LÖVE. The creators of LÖVE simply chose Lua as the language for their framework.
41 |
42 | So what part of what we code is LÖVE, and what is Lua? Very simple: Everything starting with `love.` is part of the LÖVE framework. Everything else is Lua.
43 |
44 | These are functions part of the LÖVE framework:
45 |
46 | ```lua
47 | love.graphics.circle("fill", 10, 10, 100, 25)
48 | love.graphics.rectangle("line", 200, 30, 120, 100)
49 | ```
50 |
51 | And this is simply Lua:
52 |
53 | ```lua
54 | function test(a, b)
55 | return a + b
56 | end
57 | print(test(10, 20))
58 | --Output: 30
59 | ```
60 |
61 | If it's still confusing to you, that's okay. It's not the most important thing right now.
62 |
63 | ___
64 |
65 |
66 | ## How does LÖVE work?
67 |
68 | ___
69 |
70 | *You're required to have LÖVE installed at this point. Go back to [Chapter 1](1) if you haven't already.*
71 | ___
72 |
73 | LÖVE calls 3 functions. First it calls love.load(). In here we create our variables.
74 |
75 | After that it calls love.update() and love.draw(), repeatedly in that order.
76 |
77 | So: love.load -> love.update -> love.draw -> love.update -> love.draw -> love.update, etc.
78 |
79 | Behind the scenes, LÖVE calls these functions, and we to create them, and fill them with code. This is what we call a *callback*.
80 |
81 | LÖVE is made out of *modules*, love.graphics, love.audio, love.filesystem. There are about 15 modules, and each module focuses on 1 thing. Everything that you draw is done with love.graphics. And anything with sound is done with love.audio.
82 |
83 | For now, let's focus on love.graphics.
84 |
85 | LÖVE has a [wiki](https://www.love2d.org/wiki/Main_Page) with an explanation for every function. Let's say we want to draw a rectangle. On the wiki we go to [`love.graphics`](https://www.love2d.org/wiki/love.graphics), and on the page we search for "rectangle". There we find [`love.graphics.rectangle`](https://www.love2d.org/wiki/love.graphics.rectangle).
86 |
87 | [](https://www.love2d.org/wiki/love.graphics.rectangle)
88 |
89 | This page tells us what this function does and what arguments it needs. The first argument is `mode`, and needs to be of the type `DrawMode`. We can click on [`DrawMode`](https://www.love2d.org/wiki/DrawMode) to get more info about this type.
90 |
91 | [](https://www.love2d.org/wiki/DrawMode)
92 |
93 | DrawMode is a string that is either "fill" or "line", and controls how shapes are drawn.
94 |
95 | All following arguments are numbers.
96 |
97 | So if we want to draw a filled rectangle, we can do the following:
98 | ```lua
99 | function love.draw()
100 | love.graphics.rectangle("fill", 100, 200, 50, 80)
101 | end
102 | ```
103 |
104 | Now when you run the game, you'll see a filled rectangle.
105 |
106 | 
107 |
108 | The functions that LÖVE provides is what we call the API. API stands for [Application Programming Interface](https://en.wikipedia.org/wiki/Application_programming_interface). You can read the Wikipedia article to learn what an API exactly is, but in this case it simply means the LÖVE functions you can use.
109 |
110 | ___
111 |
112 | ## Summary
113 | LÖVE is a tool that makes it easier for us to make games. LÖVE uses a programming language called Lua. Everything starting with `love.` is part of the LÖVE framework. The wiki tells us everything we need to know about how to use LÖVE.
114 |
--------------------------------------------------------------------------------
/book/chapter12.md:
--------------------------------------------------------------------------------
1 | # Chapter 12 - Images
2 |
3 | Creating and using images is a very easy task in LÖVE. First we need an image. I'm going to use this image:
4 |
5 | 
6 |
7 | Of course, you can use any image you like, as long as it's of the type *.png*. Make sure the image is in the same folder as your `main.lua`.
8 |
9 | First we need to load the image, and store it in a variable. For this we will use `love.graphics.newImage(path)`. Pass the name of the image as a string as first argument. So if you have a
10 |
11 | ```lua
12 | function love.load()
13 | myImage = love.graphics.newImage("sheep.png")
14 | end
15 | ```
16 | You can also put your image in a subdirectory, but in that case make sure to include the whole path.
17 |
18 | ```lua
19 | myImage = love.graphics.newImage("path/to/sheep.png")
20 | ```
21 |
22 | Now our image is stored inside `myImage`. We can use `love.graphics.draw` to draw our image.
23 |
24 | ```lua
25 | function love.draw()
26 | love.graphics.draw(myImage, 100, 100)
27 | end
28 | ```
29 |
30 | And that is how you draw an image.
31 |
32 | ___
33 |
34 | ## .draw() arguments
35 |
36 | Let's take a look at all the arguments of `love.graphics.draw()`. All arguments besides the image are optional.
37 |
38 | **image**
39 |
40 | The image you want to draw.
41 |
42 | **x** and **y**
43 |
44 | The horizontal and vertical position of where you want to draw the image.
45 |
46 | **r**
47 |
48 | The **r**otation (or angle). All angles in LÖVE are radians. I'll explain more about radians in another chapter.
49 |
50 | **sx** and **sy**
51 |
52 | The **x**-**s**cale and **y**-**s**cale. If you want to make your image twice as big you do
53 |
54 | `love.graphics.draw(myImage, 100, 100, 0, 2, 2)`
55 |
56 | You can also use this to mirror an image with
57 |
58 | `love.graphics.draw(myImage, 100, 100, 0, -1, 1)`
59 |
60 | **ox** and **oy**
61 |
62 | The **x**-**o**rigin and **y**-**o**rigin of the image.
63 |
64 | By default, all the scaling and rotating is based on the top-left of the image.
65 |
66 | 
67 |
68 | This is based on the *origin* of the image. If we want to scale the image from the center, we'll have to put the origin in the center of the image.
69 |
70 | `love.graphics.draw(myImage, 100, 100, 0, 2, 2, 39, 50)`
71 |
72 | 
73 |
74 | **kx** and **ky**
75 |
76 | These are for shearing (which doesn't have a **k** at all so I'm not sure what to make of it).
77 |
78 | With it you can skew images.
79 |
80 | 
81 |
82 | `love.graphics.print`, which we used before to draw text, has these same arguments.
83 |
84 | x, y, r, sx, sy, ox, oy, kx, ky
85 |
86 | Again, all these arguments except **image** can be left out. We call these *optional arguments*.
87 |
88 | You can learn about LÖVE functions by reading the [API documentation](https://love2d.org/wiki/love.graphics.draw).
89 |
90 | ___
91 |
92 | ## Image object
93 |
94 | The image that `love.graphics.newImage` returns, is actually an object. An [Image](https://love2d.org/wiki/Image) object. It has functions that we can use to edit our image, or get data about it.
95 |
96 | For example, we can use `:getWidth()` and `:getHeight()` to get the width and height of the image. We can use this to put the origin in the center of our image.
97 |
98 | ```lua
99 | function love.load()
100 | myImage = love.graphics.newImage("sheep.png")
101 | width = myImage:getWidth()
102 | height = myImage:getHeight()
103 | end
104 |
105 | function love.draw()
106 | love.graphics.draw(myImage, 100, 100, 0, 2, 2, width/2, height/2)
107 | end
108 | ```
109 |
110 | ___
111 |
112 | ## Color
113 |
114 | You can change in what color the image is drawn with `love.graphics.setColor(r, g, b)`. It sets the color for everything you draw, so not only images but rectangles, shapes and lines as well. It uses the [RGB-system](https://en.wikipedia.org/wiki/RGB_color_model). Although this officially ranges from 0 to 255, with LÖVE it ranges from 0 to 1. So instead of (255, 200, 40) you would use (1, 0.78, 0.15). If you only know the color using the 0-255 range, you can calculate the number you want with `number/255`. There is also the fourth argument `a` which stands for alpha and decides the transparency of everything you draw. Don't forget to set the color back to white if you don't want the color for any other draw calls. You can set the background color with `love.graphics.setBackgroundColor(r, g, b)`. Since we only want to call it once, we can call it in `love.load`.
115 |
116 | ```lua
117 | function love.load()
118 | myImage = love.graphics.newImage("sheep.png")
119 | love.graphics.setBackgroundColor(1, 1, 1)
120 | end
121 |
122 | function love.draw()
123 | love.graphics.setColor(255/255, 200/255, 40/255, 127/255)
124 | love.graphics.setColor(1, 0.78, 0.15, 0.5)
125 | -- Or ...
126 | love.graphics.draw(myImage, 100, 100)
127 | -- Not passing an argument for alpha automatically sets it to 1 again.
128 | love.graphics.setColor(1, 1, 1)
129 | love.graphics.draw(myImage, 200, 100)
130 | end
131 | ```
132 |
133 | 
134 |
135 | ___
136 |
137 | ## Summary
138 |
139 | We load an image with `myImage = love.graphics.newImage("path/to/image.png")`, which returns an Image object that we can store in a variable. We can pass this variable to `love.graphics.draw(myImage)` to draw the image. This function has optional arguments for the position, angle and scale of the image. An Image object has functions that you can use to get data about it. We can use `love.graphics.setColor(r, g, b)` to change in what color the image and everything else is drawn.
140 |
--------------------------------------------------------------------------------
/book/bonus/vscode.md:
--------------------------------------------------------------------------------
1 | # Visual Studio Code
2 |
3 | Visual Studio Code is a code editor by Microsoft with lots of features. In this chapter we go over some extensions and tricks to optimize the editor for making LÖVE games.
4 |
5 | [Install Visual Studio Code](https://code.visualstudio.com/)
6 |
7 | ## Template
8 |
9 | Check out Keyslam's [LÖVE VSCode Game Template](https://github.com/Keyslam/LOVE-VSCode-Game-Template) for a template. This tutorial does not use the template, but explains how you can manually add and configure the extensions that you can also find in the template.
10 |
11 | ## Extensions
12 |
13 | Install the following extensions:
14 |
15 | - [Lua by sumneko](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)
16 |
17 | - [Local Lua Debugger by Tom Blind](https://marketplace.visualstudio.com/items?itemName=tomblind.local-lua-debugger-vscode)
18 |
19 | ### Lua
20 |
21 | As the marketplace says this extension gives you lots of useful features. We can also make it have LÖVE autocomplete.
22 |
23 | Press `Ctrl + Shift + P` and look for and open `Preferences: Open User Settings (JSON)`.
24 |
25 | Add the following settings to the JSON:
26 |
27 | ```json
28 | {
29 | "Lua.runtime.version": "LuaJIT",
30 | "Lua.diagnostics.globals": [
31 | "love",
32 | ],
33 | "Lua.workspace.library": [
34 | "${3rd}/love2d/library"
35 | ],
36 | "Lua.workspace.checkThirdParty": false,
37 | }
38 | ```
39 |
40 | ### Local Lua Debugger
41 |
42 | Add the folder where `love.exe` is located to your environment variables. In Windows search for `Edit the system environment variables`. At the bottom click on `Environment Variables`. In `System variables` search for and click on `Path`. Click `Edit...`. Click `New`. Type the path to the folder where your `love.exe` is located.
43 |
44 | 
45 |
46 | Now we are going to add two launchers. Note that the following approach is not necessarily the *best*. This is a personal preference.
47 |
48 | Go to Run and Debug (play button with a bug on the left). Click on `create a launch.json file`.
49 |
50 | Replace the contents of the new file with this:
51 |
52 | ```json
53 | {
54 | "version": "0.2.0",
55 | "configurations": [
56 | {
57 | "type": "lua-local",
58 | "request": "launch",
59 | "name": "Debug",
60 | "program": {
61 | "command": "love"
62 | },
63 | "args": [
64 | ".",
65 | "debug"
66 | ],
67 | },
68 | {
69 | "type": "lua-local",
70 | "request": "launch",
71 | "name": "Release",
72 | "program": {
73 | "command": "love"
74 | },
75 | "args": [
76 | ".",
77 | ],
78 | },
79 | ]
80 | }
81 | ```
82 |
83 | Add the following line at the top of your main.lua:
84 |
85 | ```lua
86 | if arg[2] == "debug" then
87 | require("lldebugger").start()
88 | end
89 | ```
90 |
91 | You can press F5 to start the launcher. You can select which launcher you want to use in Run and Debug. You now have two ways to launch LÖVE:
92 |
93 | - With **Debug** you can debug your game. In a Lua file click on the left of a line-number to create a *break point* (and click again to remove it). When your code is at this line the debugger will stop there, and allow you to examine variables. 
94 | - With **Release** you don't have a debugger. The reason you keep these separated is so that you don't have to remember to remove the `lldebugger` line whenever you want to distribute the game.
95 |
96 | We can improve the debugger by making it highlight an error when we get one. For this we will need to edit `love.errorhandler`. LÖVE catches the error to show the nice error screen, but we want it to actually throw an error.
97 |
98 | At the bottom of `main.lua` add the following code:
99 |
100 | ```lua
101 | local love_errorhandler = love.errorhandler
102 |
103 | function love.errorhandler(msg)
104 | if lldebugger then
105 | error(msg, 2)
106 | else
107 | return love_errorhandler(msg)
108 | end
109 | end
110 | ```
111 | Now when you get an error in Debug mode Visual Studio Code will jump to the file and line of where the error occured and highlight it, along with the error message.
112 |
113 | 
114 |
115 | You might notice that your game slows down a lot in Debug mode. This only happens when you have break points, so remember to disable those if you don't use them.
116 |
117 | You can also expand on this, like showing debug information on screen when `launch_type == "debug"`.
118 |
119 | ## Building
120 |
121 | Now we want to make it easy to build our project.
122 |
123 | ### makelove
124 |
125 | We use the builder [makelove](https://github.com/pfirsich/makelove/).
126 |
127 | 1. Install [Python](https://www.python.org/downloads/) (if you do a custom installation make sure to install `pip`).
128 | 2. Open a terminal (e.g. Windows Powershell) and type `pip3 install makelove`.
129 | 3. In your game's folder (where you have your main.lua) create a file called `make_all.toml` and add the following:
130 | ```ini
131 | name = "Game"
132 | default_targets = ["win32", "win64", "macos"]
133 | build_directory = "bin"
134 | love_files = [
135 | "+*",
136 | "-*/.*",
137 | ]
138 | ```
139 |
140 | ### Tasks
141 |
142 | Press `Ctrl + Shift + P` and look for and open `Task: Configure Task`. Select `Create task.json file from template`. Select `Others` (or any other, doesn't really matter). Replace the file's contents with this:
143 | ```json
144 | {
145 | "version": "2.0.0",
146 | "tasks": [
147 | {
148 | "label": "Build LÖVE",
149 | "type": "process",
150 | "command": "makelove",
151 | "args": [
152 | "--config",
153 | "make_all.toml"
154 | ],
155 | "group": {
156 | "kind": "build",
157 | "isDefault": true
158 | }
159 | },
160 | ]
161 | }
162 | ```
163 |
164 | Now in Visual Studio Code you can press `Ctrl + Shift + B` to run the task. This will create a `bin` folder, with inside folders that have `.zip` files inside of them.
165 |
--------------------------------------------------------------------------------
/book/chapter6.md:
--------------------------------------------------------------------------------
1 | # Chapter 6 - If-statements
2 | ___
3 | *We use the code from the previous chapter*
4 | ___
5 | With if-statements, we can allow pieces of code to only be executed when a condition is met.
6 |
7 | You create an if-statement like this:
8 | ```lua
9 | if condition then
10 | -- code
11 | end
12 | ```
13 |
14 | A condition, or statement, is something that's either true or false.
15 |
16 | For example: `5 > 9`
17 |
18 | The `>` means, higher than. So the statement is that 5 is higher than 9, which is false.
19 |
20 | Put an if-statement around the code of x increasing.
21 |
22 | ```lua
23 | function love.update(dt)
24 | if 5 > 9 then
25 | x = x + 100 * dt
26 | end
27 | end
28 | ```
29 |
30 | When you run the game you'll notice that our rectangle isn't moving. This is because the statement is false. If we were to change the statement to `5 < 9` (5 is lower than 9), then the statement is true, and the code inside the if-statement will execute.
31 |
32 | With this, we can for example make `x` go up to 600, and then make it stop moving, with `x < 600`.
33 |
34 | ```lua
35 | function love.update(dt)
36 | if x < 600 then
37 | x = x + 100 * dt
38 | end
39 | end
40 | ```
41 |
42 | 
43 |
44 | If we want to check if a value is equal to another value, we need to use 2 equal-signs (==).
45 |
46 | For example: `4 == 7`
47 |
48 | 1 equal-sign is for assigning, 2 equal-signs is for comparing.
49 |
50 | ```lua
51 | x = 10 --Assign 10 to x
52 | x == 10 --Compare 10 to x
53 | ```
54 |
55 | We can also use `>=` and `<=` to check if values are higher or equal to each other or if the values are lower and equal to each other.
56 |
57 | ```lua
58 | 10 <= 10 --true, 10 equals to 10
59 | 15 >= 4 --true, 15 is higher than 4
60 | ```
61 |
62 | So the code above is a shorthand for
63 | ```lua
64 | 10 == 10 or 10 < 10
65 | 15 == 4 or 15 > 4
66 | ```
67 |
68 | ## Boolean
69 |
70 | A variable can also be `true` or `false`. This type of variable is what we call a boolean.
71 |
72 | Let's make a new variable called `move`, with the value `true`, and check if `move` is `true` in our if-statement.
73 |
74 | ```lua
75 | function love.load()
76 | x = 100
77 | move = true
78 | end
79 |
80 | function love.update(dt)
81 | -- Remember, 2 equal signs!
82 | if move == true then
83 | x = x + 100 * dt
84 | end
85 | end
86 | ```
87 |
88 | `move` is `true`, so our rectangle moves. But `move == true` is actually redundant. We're checking if it's true that the value of `move` is `true`. Simply using `move` as a statement is good enough.
89 |
90 | ```lua
91 | if move then
92 | x = x + 100 * dt
93 | end
94 | ```
95 |
96 | If we want to check if `move` is `false`, we can put a `not` in front of it.
97 |
98 | ```lua
99 | if not move then
100 | x = x + 100 * dt
101 | end
102 | ```
103 |
104 | If we want to check if a number is NOT equal to another number, we use a tilde (~).
105 |
106 | ```lua
107 | if 4 ~= 5 then
108 | x = x + 100 * dt
109 | end
110 | ```
111 |
112 | We can also assign `true` or `false` to a variable with a statement.
113 |
114 | For example: `move = 6 > 3`.
115 |
116 | If we check if move is true, and then change move to false inside the if-statement, it's not as if we jump out of the if-statement. All the code below will still be executed.
117 |
118 | ```lua
119 | if move then
120 | move = false
121 | print("This will still be executed!")
122 | x = x + 100 * dt
123 | end
124 | ```
125 |
126 | ## Arrow keys
127 | Let's make the rectangle move based on if we hold down the right arrowkey. For this we use the function `love.keyboard.isDown` [(wiki)](https://www.love2d.org/wiki/love.keyboard.isDown).
128 |
129 | Notice how the D of Down is uppercase. This is type of casing is what we call camelCasing. We start the first word in lowercase, and then every following word's first character we type in uppercase. This type of casing is also what we will be using for our variables throughout this tutorial.
130 |
131 | We pass the string "right" as first argument to check if the right arrowkey is down.
132 | ```lua
133 | if love.keyboard.isDown("right") then
134 | x = x + 100 * dt
135 | end
136 | ```
137 |
138 | So now only when we hold down the right arrowkey does our rectangle move.
139 |
140 | 
141 |
142 | We can also use `else` to tell our game what to do when the condition is `false`. Let's make our rectangle move to the left, when we don't press right.
143 |
144 | ```lua
145 | if love.keyboard.isDown("right") then
146 | x = x + 100 * dt
147 | else
148 | x = x - 100 * dt --We decrease the value of x
149 | end
150 | ```
151 |
152 | We can also check if another statement is true, after the first is false, with `elseif`. Let's make it so that after checking if the right arrowkey is down, and it's not, we'll check if the left arrowkey is down.
153 |
154 | ```lua
155 | if love.keyboard.isDown("right") then
156 | x = x + 100 * dt
157 | elseif love.keyboard.isDown("left") then
158 | x = x - 100 * dt
159 | end
160 | ```
161 | Try to make the rectangle move up and down as well.
162 |
163 | ___
164 |
165 | ## and & or
166 | With `and` we can check if multiple statements are true.
167 |
168 | ```lua
169 | if 5 < 9 and 14 > 7 then
170 | print("Both statements are true")
171 | end
172 | ```
173 |
174 | With `or`, the if-statement will execute if any of the statements is true.
175 |
176 | ```lua
177 | if 20 < 9 or 14 > 7 or 5 == 10 then
178 | print("One of these statements is true")
179 | end
180 | ```
181 |
182 |
183 | ___
184 |
185 | ## One more thing
186 | To be more precise, if-statements check if the statement is NOT `false` or `nil`.
187 | ```lua
188 | if true then print(1) end --Not false or nil, executes.
189 | if false then print(2) end --False, doesn't execute.
190 | if nil then print(3) end --Nil, doesn't execute
191 | if 5 then print(4) end --Not false or nil, executes
192 | if "hello" then print(5) end --Not false or nil, executes
193 | --Output: 1, 4, 5
194 | ```
195 |
196 | ___
197 |
198 | ## Summary
199 | With if-statements, we can allow pieces of code to only be executed when a condition is met. We can check if a number is higher, lower or equal to another number/value. A variable can be true or false. This type of variable is what we call a `boolean`. We can use `else` to tell our game what to execute when the statement was false, or `elseif` to do another check.
--------------------------------------------------------------------------------
/book/chapter13.md:
--------------------------------------------------------------------------------
1 | # Chapter 13 - Detecting collision
2 | Let's say we're making a game where you can shoot down monsters. A monster should die when it is hit by a bullet. So what we need to check is: Is the monster colliding with a bullet?
3 |
4 | We're going to create a *collision check* function. We will be checking collision between rectangles. This is called AABB collision. So we need to know, when do two rectangles collide?
5 |
6 | I created an image with three examples:
7 |
8 | 
9 |
10 | It's time to turn on that programmer brain if you haven't already. What is going on in the third example that isn't happening in the first and second example?
11 |
12 | "They are colliding"
13 |
14 | Yes, but you have to be more specific. We need information that the computer can use.
15 |
16 | Take a look at the positions of the rectangles. In the first example, Red is not colliding with Blue, because Red is too far to the left. If Red was a bit further to the right, they would touch. How far exactly? Well, if **Red's right side** is further to the **right** than **Blue's left side**. This is something that is true for example 3.
17 |
18 | But it's also true for example 2. We need more conditions to be sure there is collision. So example 2 shows we can't go too far to the right. How far exactly can we go? How much would Red have to move to the left for there to be collision? When **Red's left side** is further to the **left** than **Blue's right side**.
19 |
20 | So we have two conditions, is that enough to ensure there is collision?
21 |
22 | Well no, look at the following image:
23 |
24 | 
25 |
26 | This situation agrees with our conditions. Red's right side is further to the right than Blue's left side. And Red's left side is further to the left than Blue's right side. Yet, there is no collision. That's because Red is too high. It needs to move down. How far? Till **Red's bottom side** is further to the **bottom** than **Blue's top side**.
27 |
28 | But if we move it too far down, there won't be collision anymore. How far can Red move down, and still collide with Blue? As long as **Red's top side** is further to the **top** than **Blue's bottom side**.
29 |
30 | Now we got four conditions. Are all four conditions true for these three examples?
31 |
32 | 
33 |
34 | **Red's right side** is further to the **right** than **Blue's left side**.
35 |
36 | **Red's left side** is further to the **left** than **Blue's right side**.
37 |
38 | **Red's bottom side** is further to the **bottom** than **Blue's top side**.
39 |
40 | **Red's top side** is further to the **top** than **Blue's bottom side**.
41 |
42 | Yes, they are! Now we need to turn this information into a function.
43 |
44 | First let's create two rectangles.
45 |
46 | ```lua
47 | function love.load()
48 | --Create 2 rectangles
49 | r1 = {
50 | x = 10,
51 | y = 100,
52 | width = 100,
53 | height = 100
54 | }
55 |
56 | r2 = {
57 | x = 250,
58 | y = 120,
59 | width = 150,
60 | height = 120
61 | }
62 | end
63 |
64 |
65 | function love.update(dt)
66 | --Make one of rectangle move
67 | r1.x = r1.x + 100 * dt
68 | end
69 |
70 |
71 | function love.draw()
72 | love.graphics.rectangle("line", r1.x, r1.y, r1.width, r1.height)
73 | love.graphics.rectangle("line", r2.x, r2.y, r2.width, r2.height)
74 | end
75 | ```
76 |
77 | Now we create a new function called checkCollision(), with 2 rectangles as parameters.
78 |
79 | ```lua
80 | function checkCollision(a, b)
81 |
82 | end
83 | ```
84 |
85 | First we need the sides of the rectangles. The left side is the x position, the right side is the x position + the width. Same with y and height.
86 |
87 | ```lua
88 | function checkCollision(a, b)
89 | --With locals it's common usage to use underscores instead of camelCasing
90 | local a_left = a.x
91 | local a_right = a.x + a.width
92 | local a_top = a.y
93 | local a_bottom = a.y + a.height
94 |
95 | local b_left = b.x
96 | local b_right = b.x + b.width
97 | local b_top = b.y
98 | local b_bottom = b.y + b.height
99 | end
100 | ```
101 |
102 | Now that we have the four sides of each rectangle, we can use them to put our conditions in an if-statement.
103 |
104 | ```lua
105 | function checkCollision(a, b)
106 | --With locals it's common usage to use underscores instead of camelCasing
107 | local a_left = a.x
108 | local a_right = a.x + a.width
109 | local a_top = a.y
110 | local a_bottom = a.y + a.height
111 |
112 | local b_left = b.x
113 | local b_right = b.x + b.width
114 | local b_top = b.y
115 | local b_bottom = b.y + b.height
116 |
117 | --If Red's right side is further to the right than Blue's left side.
118 | if a_right > b_left
119 | --and Red's left side is further to the left than Blue's right side.
120 | and a_left < b_right
121 | --and Red's bottom side is further to the bottom than Blue's top side.
122 | and a_bottom > b_top
123 | --and Red's top side is further to the top than Blue's bottom side then..
124 | and a_top < b_bottom then
125 | --There is collision!
126 | return true
127 | else
128 | --If one of these statements is false, return false.
129 | return false
130 | end
131 | end
132 | ```
133 |
134 | Notice that the if-condition itself is a boolean value. `checkCollision` returns `true` when the if-condition is `true` and vise-versa. Therefore `checkCollision` can be simplified into the following form:
135 |
136 | ```lua
137 | function checkCollision(a, b)
138 | --With locals it's common usage to use underscores instead of camelCasing
139 | local a_left = a.x
140 | local a_right = a.x + a.width
141 | local a_top = a.y
142 | local a_bottom = a.y + a.height
143 |
144 | local b_left = b.x
145 | local b_right = b.x + b.width
146 | local b_top = b.y
147 | local b_bottom = b.y + b.height
148 |
149 | --Directly return this boolean value without using if-statement
150 | return a_right > b_left
151 | and a_left < b_right
152 | and a_bottom > b_top
153 | and a_top < b_bottom
154 | end
155 | ```
156 |
157 | Okay, we have our function. Let's try it out! We draw the rectangles filled or lined based on
158 |
159 | ```lua
160 | function love.draw()
161 | --We create a local variable called mode
162 | local mode
163 | if checkCollision(r1, r2) then
164 | --If there is collision, draw the rectangles filled
165 | mode = "fill"
166 | else
167 | --else, draw the rectangles as a line
168 | mode = "line"
169 | end
170 |
171 | --Use the variable as first argument
172 | love.graphics.rectangle(mode, r1.x, r1.y, r1.width, r1.height)
173 | love.graphics.rectangle(mode, r2.x, r2.y, r2.width, r2.height)
174 | end
175 | ```
176 |
177 | It works! Now you know how to detect collision between two rectangles.
178 |
179 | ___
180 |
181 | ## Summary
182 |
183 | Collision between two rectangles can be checked with four conditions.
184 |
185 | Where A and B are rectangles:
186 |
187 | A's right side is further to the right than B's left side.
188 |
189 | A's left side is further to the left than B's right side.
190 |
191 | A's bottom side is further to the bottom than B's top side.
192 |
193 | A's top side is further to the top than B's bottom side.
194 |
--------------------------------------------------------------------------------
/book/chapter9.md:
--------------------------------------------------------------------------------
1 | # Chapter 9 - Multiple files and scope
2 |
3 | ## Multiple files
4 | With multiple files our code will look more organized and easier to navigate. Create a new file called `example.lua`. Make sure it's in the same folder as a new and empty `main.lua`.
5 |
6 | Inside this file, create a variable. I will put `--! file:` at the top of the codeblock to make clear in what file you have to put the code. This is only for this tutorial, it has no use (it's only commentary after all) and you don't need to copy it. If a codeblock doesn't have this in future tutorials, it's either in main.lua, or the previous mentioned file.
7 |
8 | ```lua
9 | --! file: example.lua
10 | test = 20
11 | ```
12 |
13 | Now in `main.lua`, put `print(test)`. When you run the game, you'll see that `test` equals `nil`. This is because we have to load the file first. We do this with `require`, by passing the filename as string as first argument.
14 |
15 | ```lua
16 | --! file: main.lua
17 | --Leave out the .lua
18 | -- No need for love.load or whatever
19 | require("example")
20 | print(test)
21 | ```
22 |
23 | We don't add the `".lua"` in the filename, because Lua does this for us.
24 |
25 | You can also put the file in a subdirectory, but in that case make sure to include the whole path.
26 |
27 | ```lua
28 | --With require we use . instead of /
29 | require("path.to.example")
30 | ```
31 |
32 | Now when you print `test`, after we loaded `example.lua`, you should see it says 20.
33 |
34 | `test` in this case is what we call a *global variable* (or a *global* for short). It's a variable that we can use everywhere in our project. The opposite of a global variable, is a *local variable* (or *local* for short). You create a local variable by writing `local` in front of the variable name.
35 |
36 | ```lua
37 | --! file: example.lua
38 | local test = 20
39 | ```
40 | Run the game to see test is now `nil` again. This is because of its *scope*.
41 |
42 | ___
43 |
44 | ## Scope
45 |
46 | Local variables are limited to their *scope*. In the case of `test`, the scope is the file `example.lua`. This means that `test` can be used everywhere inside that file, but not in other files.
47 |
48 | If we were to create a local variable inside a *block*, like a function, if-statement or for-loop, then that would be the variable's scope.
49 |
50 | ```lua
51 | --! file: example.lua
52 | if true then
53 | local test = 20
54 | end
55 |
56 | print(test)
57 | --Output: nil
58 | ```
59 |
60 | `test` is `nil`, because we print it outside of its scope.
61 |
62 | Parameters of functions are like local variables. Only existing inside the function.
63 |
64 | To really understand how scope works, take a look at the following code:
65 |
66 | ```lua
67 | --! file: main.lua
68 | test = 10
69 | require("example")
70 | print(test)
71 | --Output: 10
72 | ```
73 |
74 | ```lua
75 | --! file: example.lua
76 | local test = 20
77 |
78 | function some_function(test)
79 | if true then
80 | local test = 40
81 | print(test)
82 | --Output: 40
83 | end
84 | print(test)
85 | --Output: 30
86 | end
87 |
88 | some_function(30)
89 |
90 | print(test)
91 | --Output: 20
92 | ```
93 |
94 | If you run the game, it should print: 40, 30, 20, 10. Let's take a look at this code step by step.
95 |
96 | First we create the variable `test` in `main.lua`, but before we print it we require `example.lua`.
97 |
98 | In `example.lua` we create a local variable `test`, which does not affect the global variable in `main.lua`. Meaning that the value we give to this local variable we create is not given to the global variable.
99 |
100 | We create a function called `some_function(test)` and then call that function.
101 |
102 | Inside that function the parameter `test` does not affect the local variable we created earlier.
103 |
104 | Inside the if-statement we create another local variable called `test`, which does not affect the parameter `test`.
105 |
106 | The first print is inside the if-statement, and it's 40. After the if-statement we print `test` again, and now it's 30, which is what we passed as argument. The parameter `test` was not affected by the `test` inside the if-statement. Inside the if-statement the local variable took priority over the parameter.
107 |
108 | Outside of the function we also print `test`. This time it's 20. The `test` created at the start of `example.lua` was not affected by the `test` inside the function.
109 |
110 | And finally we print `test` in `main.lua`, and it's 10. The global variable was not affected by the local variables inside `example.lua`.
111 |
112 | I made a visualization of the scope of each `test` to make it even more clear:
113 |
114 | 
115 |
116 | When creating a local variable, you don't have to assign a value right away.
117 |
118 | ```lua
119 | local test
120 | test = 20
121 | ```
122 |
123 | ## Returning a value
124 |
125 | If you add a return statement at the top scope of a file (so not in any function) it will be returned when you use `require` to load the file.
126 |
127 | So for example:
128 |
129 | ```lua
130 | --! file: example.lua
131 | local test = 99
132 | return test
133 | ```
134 | ```lua
135 | --! file: main.lua
136 | local hello = require "example"
137 | print(hello)
138 | --Ouput: 99
139 | ```
140 |
141 | ## When and why locals?
142 |
143 | The best practice is to always use local variables. The main reason for this is that with globals you're more likely to make mistakes. You might accidentally use the same variable twice at different locations, changing the variable to something at location 1 where it won't make sense to have that value at location 2. If you're going to use a variable only in a certain scope then make it local.
144 |
145 | In the previous chapter we made a function that creates rectangles. In this function we could have made the variable `rect` local, since we only use it in that function. We still use that rectangle outside the function, but we access it from the table `listOfRectangles` to which we add it.
146 |
147 | We don't make `listOfRectangles` local because we use it in multiple functions.
148 |
149 | ```lua
150 | function love.load()
151 | listOfRectangles = {}
152 | end
153 |
154 | function createRect()
155 | local rect = {}
156 | rect.x = 100
157 | rect.y = 100
158 | rect.width = 70
159 | rect.height = 90
160 | rect.speed = 100
161 |
162 | -- Put the new rectangle in the list
163 | table.insert(listOfRectangles, rect)
164 | end
165 | ```
166 |
167 | Though we could still make it local by creating the variable outside of the `love.load` function.
168 |
169 | ```lua
170 | -- By declaring it here we can access it everywhere in this file.
171 | local listOfRectangles = {}
172 |
173 | function love.load()
174 | -- It's empty so we could remove this function now
175 | end
176 | ```
177 |
178 | So are there moments when it is okay to use globals? People have mixed opinions on this. Some people will tell you never to use globals. I'll tell you that it's fine, especially as a beginner, to use global variables when you need them in multiple files. Preferably these are variables that you don't plan to change after creating them (reassignment). Similarly to how `love` is a global variable that never changes.
179 |
180 | Note that throughout this tutorial I use a lot of globals, but this is to make the code smaller and easier to explain.
181 |
182 | ___
183 |
184 | ## Summary
185 | With `require` we can load other lua-files. When you create a variable you can use it in all files. Unless you create a local variable, which is limited to its scope. Local variables do not affect variables with the same name outside of their scope. Always try use local variables over global variables, as they are faster.
186 |
--------------------------------------------------------------------------------
/book/chapter7.md:
--------------------------------------------------------------------------------
1 | # Chapter 7 - Tables and for-loops
2 |
3 | ## Tables
4 | Tables are basically lists in which we can store values.
5 |
6 | You create a table with curly brackets ({ }):
7 |
8 | ```lua
9 | function love.load()
10 | fruits = {}
11 |
12 | end
13 | ```
14 |
15 | We just created a table called fruits. Now we can store values inside the table. There are multiple ways to do this.
16 |
17 | One way is to put the values inside the curly brackets.
18 |
19 | ```lua
20 | function love.load()
21 | -- Each value is separated by a comma, just like with parameters and arguments
22 | fruits = {"apple", "banana"}
23 | end
24 | ```
25 |
26 | We can also use the function `table.insert`. The first argument is the table, the second argument is the value we want to store inside that table.
27 |
28 | ```lua
29 | function love.load()
30 | --Each value is separated by a comma, just like with parameters and arguments
31 | fruits = {"apple", "banana"}
32 | table.insert(fruits, "pear")
33 | end
34 | ```
35 |
36 | So now after love.load our table will contain `"apple"`, `"banana"` and `"pear"`. To prove that, let's put the values on screen. For that we're going to use `love.graphics.print(text, x, y)`.
37 |
38 | ```lua
39 | function love.draw()
40 | --Arguments: (text, x-position, y-position)
41 | love.graphics.print("Test", 100, 100)
42 | end
43 | ```
44 |
45 | When you run the game, you should see the text "test" written. We can access the values of our table by writing the tables name, followed by brackets ([ ]) (So not curly but square brackets!). Inside these brackets, we write the position of the value we want.
46 |
47 | 
48 |
49 | Like I said, tables are a list of values. We first added `"apple"` and `"banana"`, so those are on the first and second position in the list. Next we added `"pear"`, so that's on the third position in the list. On position 4 there is no value (yet), since we only added 3 values.
50 |
51 | So if we want to print `"apple"`, we have to get the first value of the list.
52 |
53 | ```lua
54 | function love.draw()
55 | love.graphics.print(fruits[1], 100, 100)
56 | end
57 | ```
58 |
59 | And so now it should draw `"apple"`. If you replace the `[1]` with `[2]`, you should get `"banana"`, and with `[3]` you get `"pear"`.
60 |
61 | Now we want to draw all 3 fruits. We could use love.graphics.print 3 times, each with a different table entry...
62 |
63 | ```lua
64 | function love.draw()
65 | love.graphics.print(fruits[1], 100, 100)
66 | love.graphics.print(fruits[2], 100, 200)
67 | love.graphics.print(fruits[3], 100, 300)
68 | end
69 | ```
70 |
71 | ...but imagine if we had 100 values in our table. Luckily, there's a solution for this: for-loops!
72 |
73 | ___
74 |
75 | ## for-loops
76 |
77 | A for-loop is a way to repeat a piece of code a certain amount of times.
78 |
79 | You create a for-loop like this:
80 |
81 | ```lua
82 | function love.load()
83 | fruits = {"apple", "banana"}
84 | table.insert(fruits, "pear")
85 |
86 | for i=1,10 do
87 | print("hello", i)
88 | end
89 | end
90 | ```
91 |
92 | If you run the game you should see it prints hello 1, hello 2, hello 3, all the way to 10.
93 |
94 | To create a for-loop, first you write `for`. Next you write a variable and give it a numeric value. This is where the for-loop starts. The variable can be named anything, but it's common to use `i`. This variable can only be used inside the for-loop (just like with functions and parameters). Next you write the number to which it should count. So for example `for i=6,18 do` will start at 6 and keep looping till it's at 18.
95 |
96 | There is also a third, optional number. This is by how much the variable increases. `for i=6,18,4 do` would go: 6, 10, 14, 18. With this you can also make for-loops go backwards with -1.
97 |
98 | Our table starts at 1 and has 3 values, so we will write:
99 |
100 | ```lua
101 | function love.load()
102 | fruits = {"apple", "banana"}
103 | table.insert(fruits, "pear")
104 |
105 | for i=1,3 do
106 | print(fruits[i])
107 | end
108 | end
109 | ```
110 |
111 | When you run the game you'll see that it prints all 3 fruits. In the first loop it prints `fruits[1]`, then in the second loop `fruits[2]`and finally in the third loop `fruits[3]`.
112 |
113 | ___
114 |
115 | ## Editing tables
116 |
117 | But what if we add or remove a value from a table? We would have to change the 3 into another number. For that we use `#fruits`. With the #-sign, we can get the length of a table. The length of a table refers to the number of things in that table. That length would be `3` in our case, since we have 3 entries: `apple`, `banana`, and `pear` in our `fruits` table.
118 |
119 | ```lua
120 | function love.load()
121 | fruits = {"apple", "banana"}
122 |
123 | print(#fruits)
124 | --Output: 2
125 |
126 | table.insert(fruits, "pear")
127 |
128 | print(#fruits)
129 | --Output: 3
130 |
131 | for i=1,#fruits do
132 | print(fruits[i])
133 | end
134 | end
135 | ```
136 |
137 | Let's use this knowledge to draw all 3 fruits.
138 |
139 | ```lua
140 | function love.draw()
141 | for i=1,#fruits do
142 | love.graphics.print(fruits[i], 100, 100)
143 | end
144 | end
145 | ```
146 |
147 | If you run the game you should see it draws all 3 fruits, except they're all drawn on the same position. We can fix this by printing each number on a different height.
148 |
149 | ```lua
150 | function love.draw()
151 | for i=1,#fruits do
152 | love.graphics.print(fruits[i], 100, 100 + 50 * i)
153 | end
154 | end
155 | ```
156 |
157 | So now `"apple"` will be drawn on the y-position 100 + 50 * 1, which is 150. Then `"banana"` gets drawn on 200, and `"pear"` on 250.
158 |
159 | 
160 |
161 | If we were to add another fruit, we won't have to change anything. It will automatically be drawn. Let's add `"pineapple"`.
162 |
163 | ```lua
164 | function love.load()
165 | fruits = {"apple", "banana"}
166 | table.insert(fruits, "pear")
167 | table.insert(fruits, "pineapple")
168 | end
169 | ```
170 |
171 | We can also remove values from our table. For that we use `table.remove`. The first argument is the table we want to remove something from, the second argument is the position we want to remove. So if we want to remove banana, we do the following:
172 |
173 | ```lua
174 | function love.load()
175 | fruits = {"apple", "banana"}
176 | table.insert(fruits, "pear")
177 | table.insert(fruits, "pineapple")
178 | table.remove(fruits, 2)
179 | end
180 | ```
181 |
182 | When you run the game you'll see that banana is no longer drawn, and that pear and pineapple have moved up.
183 |
184 | 
185 |
186 | When you remove a value from a table with `table.remove`, all the following items in the table will move up. So what was on position 3 is now on position 2 in the table. And what was on position 4 is now on position 3.
187 |
188 | You can also add or change the values inside the table directly. For example, we can change `"apple"` into `"tomato"`:
189 |
190 | ```lua
191 | function love.load()
192 | fruits = {"apple", "banana"}
193 | table.insert(fruits, "pear")
194 | table.insert(fruits, "pineapple")
195 | table.remove(fruits, 2)
196 | --The value of position 1 in the table becomes "tomato"
197 | fruits[1] = "tomato"
198 | end
199 | ```
200 |
201 | ___
202 |
203 | ## ipairs
204 |
205 | Back to the for-loops. There is actually another way, and an easier way to loop through the table. We can use an `ipairs` loop.
206 |
207 | ```lua
208 | function love.load()
209 | fruits = {"apple", "banana"}
210 | table.insert(fruits, "pear")
211 | table.insert(fruits, "pineapple")
212 | table.remove(fruits, 2)
213 | fruits[1] = "tomato"
214 |
215 | for i,v in ipairs(fruits) do
216 | print(i, v)
217 | end
218 | --Output:
219 | --1, "tomato"
220 | --2, "pear"
221 | --3, "pineapple"
222 | end
223 | ```
224 |
225 | This for-loop loops, or what we also call *iterates*, through all the values in the table. The variables `i` tells us the position of the table, `v` is the value of that position in the table. It's basically a shorthand for `fruits[i]`. For example, in the first iteration the values for the variables `i` would be `1` and `v` would be `"apple"`. In the second iteration, `i` and `v` would be `2` and `"pear"` respectively.
226 |
227 | But how does it work? Why does the function `ipairs` allow for this? That is for another time. For now all you need to know is that `ipairs` is basically a shorthand for the following:
228 |
229 | ```lua
230 | for i=1, #fruits do
231 | v = fruits[i]
232 | end
233 | ```
234 |
235 | Let's use `ipairs` for drawing our tables.
236 |
237 | ```lua
238 | function love.draw()
239 | -- i and v are variables, so we can name them whatever we want
240 | for i,frt in ipairs(fruits) do
241 | love.graphics.print(frt, 100, 100 + 50 * i)
242 | end
243 | end
244 | ```
245 |
246 | ___
247 |
248 | ## Summary
249 | Tables are lists in which we can store values. We store these values when creating the table, with `table.insert`, or with `table_name[1] = "some_value"`. We can get the length of the table with `#table_name`. With for-loops we can repeat a piece of code a number of times. We can also use for-loops to iterate through tables.
250 |
--------------------------------------------------------------------------------
/book/chapter20.md:
--------------------------------------------------------------------------------
1 | # Chapter 20 - Debugging
2 | A bug is something that goes wrong in a program (or in our case game). Debugging is the art of fixing these bugs so that they don't occur anymore. As a programmer it's only natural to encounter lots of bugs, so debugging is a very valuable skill to have.
3 |
4 | I wrote a short game.
5 |
6 | ```lua
7 | function love.load()
8 | circle = {x = 100, y = 100}
9 | bullets = {}
10 | end
11 |
12 | function love.update(dt)
13 | for i,v in ipairs(bullets) do
14 | v.x = v.x + 400 * dt
15 | end
16 | end
17 |
18 | function love.draw()
19 | love.graphics.circle("fill", circle.x, circle.y, 50)
20 |
21 | for i,v in ipairs(bullets) do
22 | love.graphics.circle("fill", v.x, v.y, 10)
23 | end
24 | end
25 |
26 | function love.keypressed()
27 | if key == "space" then
28 | shoot()
29 | end
30 | end
31 |
32 | function shoot()
33 | table.insert(bullets, {circle.x, circle.y})
34 | end
35 | ```
36 |
37 | When you press `space` it will shoot a bullet. Or at least that's what is supposed to happen, but it doesn't work. I don't see any bullets appearing. Let's try to find out why that is.
38 |
39 | Here are some of the reasons I can think of for why it's not working.
40 |
41 | * It doesn't shoot the bullets.
42 | * It's shooting the bullets, but it's not drawing them correctly.
43 | * It's drawing the bullets, but they are in the wrong position.
44 |
45 | To figure out where it goes wrong we can use `print`. For example, let's use `print` inside the for-loop in `love.update` to check the x-position of the circle, and if it even gets there to begin with. Because if the bullets aren't spawning, the `print` will never be reached.
46 |
47 |
48 | ```
49 | function love.update(dt)
50 | for i,v in ipairs(bullets) do
51 | v.x = v.x + 400 * dt
52 | print(v.x)
53 | end
54 | end
55 | ```
56 |
57 | Remember that you can add the following code at the top of your `main.lua` to have the output appear right away, and you don't need to close your game for it.
58 |
59 | ```lua
60 | io.stdout:setvbuf("no")
61 | ```
62 |
63 | Run the game and press space a few times to shoot. As you can see there are no prints in your output panel. Looks like we're not filling our `bullets` table. Let's make sure that we actually call the method `shoot` by putting a `print` there.
64 |
65 | ```lua
66 | function shoot()
67 | table.insert(bullets, {circle.x, circle.y})
68 |
69 | -- Did you know that print takes infinite amount of arguments?
70 | print("How many bullets?", #bullets)
71 | end
72 | ```
73 |
74 | Try to shoot again and you'll see that we still don't see anything printed to your output panel. Weird, because our if-statement says `if key == "space"`, and we're definitely pressing space. But to be sure, let's print the value of `key`. And who knows, maybe we spelled `love.keypressed` wrong and it doesn't reach that code at all.
75 |
76 | ```lua
77 | function love.keypressed()
78 | -- Adding texts like these gives context to your print.
79 | -- This is especially useful when you have multiple prints.
80 | print("What is the key?", key)
81 | if key == "space" then
82 | shoot()
83 | end
84 | end
85 | ```
86 |
87 | When you try to shoot again you'll see that now there is something being printed. Looks like the value of `key` is `nil`. How is that possible, because LÖVE passes the key as first argument. But wait, we don't have a parameter called `key`. I forgot to add it when making the function. So let's fix that.
88 |
89 | ```lua
90 | function love.keypressed(key)
91 | print("What is the key?", key)
92 | if key == "space" then
93 | shoot()
94 | end
95 | end
96 | ```
97 |
98 | Okay so now when you press space, it still doesn't shoot, but something else happens. You get an error.
99 |
100 | ___
101 |
102 | ## Reading and fixing errors
103 |
104 | An error occurs when the code is trying to execute something that isn't possible. For example, you can't multiply a number by a string. This will give you an error
105 |
106 | ```lua
107 | print(100 * "test")
108 | ```
109 | 
110 |
111 | Another example is trying to call a function that doesn't exist.
112 |
113 | ```lua
114 | doesNotExist()
115 | ```
116 |
117 | 
118 |
119 | In our bullet shooting game we get the following error:
120 |
121 | 
122 |
123 | So what exactly does the error tell us? Because a mistake that a lot of beginners make is that they don't realize that the error message tells you exactly why and where the error occurs.
124 |
125 | ```
126 | main.lua:10:
127 | ```
128 |
129 | This tells us the error occurs on line 10 (this may be a different line for you).
130 |
131 | ```
132 | attempt to perform arithmetic on field 'x' (a nil value)
133 | ```
134 |
135 | Arithmetic means a calculation, e.g. using `+`, `-`, `*`, etc. It tried to calculate using the field `x`, but `x` is a `nil` value.
136 |
137 | So that's weird, because we give the table an `x` and `y` value, right? Well no. We add the values to the table, but we don't assign them to a field. Let's fix that.
138 |
139 | ```
140 | function shoot()
141 | table.insert(bullets, {x = circle.x, y = circle.y})
142 | end
143 | ```
144 |
145 | And now it finally works. We can shoot bullets!
146 |
147 | Let's look at some new code where I create a circle class, and draw it a few times (you don't necessarily have to write along).
148 |
149 | ```lua
150 | Object = require "classic"
151 |
152 | Circle = Object:extend()
153 |
154 | function Circle:new()
155 | self.x = 100
156 | self.y = 100
157 | end
158 |
159 | function Circle:draw(offset)
160 | love.graphics.circle("fill", self.x + offset, self.y, 25)
161 | end
162 |
163 | function love.load()
164 | circle = Circle()
165 | end
166 |
167 | function love.draw()
168 | circle:draw(10)
169 | circle:draw(70)
170 | circle.draw(140)
171 | circle:draw(210)
172 | end
173 | ```
174 |
175 | Upon running this I get the following error:
176 |
177 | 
178 |
179 | "Attempt to index" means it tried to find a property of something. So in this case it tried to find the property `x` on the variable `self`. But according to the error, `self` is a number value, so it can't do that. So how did this happen? We use a colon (:) for our function so that it automatically has `self` as first parameter, and we call the function with a colon so that it passes `self` as first argument. But apparently somewhere something went wrong. To know where it went wrong, we can use the Traceback.
180 |
181 | The bottom part of the error tells us the "path" it took before reaching the error. It's read from down to top. You can ignore the `xpcall` line. The next line says `main.lua:21: in function 'draw'`. Interesting, let's take a look. Ah yes, I see. On line 21 I used a dot instead of a colon (`circle.draw(140)`). I changed it to a colon and now it works!
182 |
183 | ___
184 |
185 | ## Syntax errors
186 |
187 | A *syntax error* occurs when the game can't start to begin with, because something is wrong with the code. It tries to "read" the code but can't understand it. For example, remember how you can't have a variable name starting with a number? When you try to do so it will give you an error:
188 |
189 | 
190 |
191 | Take a look at the following code (again, no need to type along)
192 |
193 | ```lua
194 | function love.load()
195 | timer = 0
196 | show = true
197 | end
198 |
199 | function love.update(dt)
200 | show = false
201 | timer = timer + dt
202 |
203 | if timer > 1 then
204 | if love.keyboard.isDown("space") then
205 | show = true
206 | end
207 | if timer > 2 then
208 | timer = 0
209 | end
210 | end
211 |
212 | function love.draw()
213 | if show then
214 | love.graphics.rectangle("fill", 100, 100, 100, 100)
215 | end
216 | end
217 | ```
218 |
219 | It gives me the following error:
220 |
221 | 
222 |
223 | `` means *end of file*. It expects an `end` at the end of the file. Is that how we fix it, placing an `end` at the end of the file? Well no. Somewhere I messed up, and we need to fix it correctly. It says that it expects the `end` to close the function at line 6, so let's start from there and go down. After opening the function I start an if-statement, and then another if-statement. I close the second if-statement and start another if-statement. I close that if-statement as well and then close the outer if-statement. No, wait, that's not right. I should be closing the function at that point. I forgot to add an `end` somewhere in the function. Let me fix that.
224 |
225 | ```
226 | function love.update(dt)
227 | show = false
228 | timer = timer + dt
229 |
230 | if timer > 1 then
231 | if love.keyboard.isDown("space") then
232 | show = true
233 | end
234 | if timer > 2 then
235 | timer = 0
236 | end
237 | end
238 | end
239 | ```
240 |
241 | And now it works. This is why indentation in your code is so important. It helps you see where you made a mistake like this one.
242 |
243 | Another common mistake is one like the following:
244 |
245 | ```
246 | function love.load()
247 | t = {}
248 | table.insert(t, {x = 100, y = 50)
249 | end
250 | ```
251 |
252 | Which gives me the follow error:
253 |
254 | 
255 |
256 | It's because I didn't close the curly brackets.
257 |
258 | ```
259 | function love.load()
260 | t = {}
261 | table.insert(t, {x = 100, y = 50})
262 | end
263 | ```
264 |
265 | ___
266 |
267 | ## Asking for help
268 |
269 | Now maybe you have a bug, and you can't fix it. You tried debugging it but you just can't figure out why it won't work, and you feel like you need help. Well then lucky for you there are plenty of people on the internet that are happy to help you. The best places to ask your question are on [forums](https://www.love2d.org/forums/viewforum.php?f=4&sid=4764a2d3dfb4e22494fe4e6de08ec829) or in the [Discord](https://discord.gg/MHtXaxQ). But asking a question is not simply asking "Hey guys I got this bug where this happens, what do I do?". You need to provide them information that they can use to help you.
270 | For example:
271 |
272 | * What exactly goes wrong and what do you expect/want to happen?
273 | * Explain what you have tried to do so far to fix it.
274 | * Show the code of where (you think) it goes wrong.
275 | * Share the .love file so that other people can try it out themselves.
276 |
277 | But before you ask for help you may want to search for your question. It just might be that it's a common question and it has been answered multiple times.
278 |
279 | And remember, no one is obligated to help you, and the ones that do all do it for free, so be kind :)
280 |
281 | ___
282 |
283 | ## Rubber duck debugging
284 |
285 | You could also get a rubber duck. There's this thing called [rubber duck debugging](https://en.wikipedia.org/wiki/Rubber_duck_debugging). The idea is that when you explain what you're doing in your code, you often realize what you're doing wrong and fix the bug yourself. So instead of explaining it to someone, you explain it to your rubber duck. I have rubber duck myself, his name is Hendrik!
286 |
287 | 
288 |
289 | ___
290 |
291 | ## Summary
292 | We can use `print` to find the source of our bug. Error message tell us exactly what is going wrong. Syntax errors are errors that occur because it can't "read" the code correctly. Indentation is useful because it prevents us from getting end of line-errors. We can ask for help on the internet but we should provide enough information on what is going on to make it less difficult on the people helping us.
293 |
--------------------------------------------------------------------------------
/book/chapter16.md:
--------------------------------------------------------------------------------
1 | # Chapter 16 - Angles and distance
2 |
3 | ## Angle
4 |
5 | Let's make a circle that moves in the direction of our mouse cursor.
6 |
7 | Start with creating a circle.
8 |
9 | ```lua
10 | function love.load()
11 | --Create an object called circle
12 | circle = {}
13 |
14 | --Give it the properties x, y, radius and speed
15 | circle.x = 100
16 | circle.y = 100
17 | circle.radius = 25
18 | circle.speed = 200
19 | end
20 |
21 |
22 | function love.draw()
23 | --Draw the circle
24 | love.graphics.circle("line", circle.x, circle.y, circle.radius)
25 | end
26 | ```
27 |
28 | To move the circle towards the cursor, we need to know the angle. We can get the angle with the function `math.atan2`. The first argument is the y-position you want to go to, minus your object's y-position. The second argument is the same but for the x-position. This is one of the rare occasions where y comes before x.
29 |
30 | Basically what atan2 does is that it takes a vertical and horizontal vector (distance + direction), and with that information it returns an angle.
31 |
32 | 
33 |
34 | To get the velocity we need, we subtract our circle's position from our targets position.
35 |
36 | ```lua
37 | function love.update(dt)
38 | --love.mouse.getPosition returns the x and y position of the cursor.
39 | mouse_x, mouse_y = love.mouse.getPosition()
40 |
41 | angle = math.atan2(mouse_y - circle.y, mouse_x - circle.x)
42 | end
43 |
44 |
45 | function love.draw()
46 | love.graphics.circle("line", circle.x, circle.y, circle.radius)
47 |
48 | --Print the angle
49 | love.graphics.print("angle: " .. angle, 10, 10)
50 |
51 | --Here are some lines to visualize the velocities
52 | love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
53 | love.graphics.line(circle.x, circle.y, circle.x, mouse_y)
54 |
55 | --The angle
56 | love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
57 | end
58 | ```
59 |
60 | 
61 |
62 | If atan2 confuses you, don't worry. All you need to know is: `math.atan2(target_y - object_y, target_x - object_x)` gives you the angle. In our case the object is the circle and the target is our cursor.
63 |
64 | It's about to get mathy in here, but don't let it frighten you. It's not that difficult, and if you don't understand that's completely fine at a beginner level.
65 |
66 | When you run the game you might notice that the angle is not going higher than 3.14 (Pi, π). This is because atan2 doesn't return an angle in degrees, but instead it returns the angle in radians.
67 |
68 | This is a gif explaining radians.
69 |
70 | 
71 |
72 | If you're still confused I recommend watching [Khan Academy's video](https://www.youtube.com/watch?v=EnwWxMZVBeg) on radians.
73 |
74 | Some keypoints:
75 |
76 | * math.atan2 returns an angle in radians.
77 | * The returned angle is between -3.14 and 3.14.
78 | * 360 degrees equals π*2 radians. So 90 degrees equals π/2 radians.
79 |
80 |
81 | The number π, sometimes written as Pi, is the ratio of a circle's circumference to its diameter. Meaning that if we were to take the diameter of a circle, and divide it by the circumference of the circle, we get Pi.
82 |
83 | 
84 |
85 | In Lua we can get π by using `math.pi`.
86 |
87 | If you don't understand that's fine for now. Don't be discouraged if you don't get something the first time.
88 |
89 | ___
90 |
91 | ## Sine and cosine
92 |
93 | Now we need to make our circle move towards the mouse. For this we will use `math.cos` and `math.sin`.
94 |
95 | Both functions will return a number between -1 and 1 based on the angle we pass.
96 |
97 | Here's a gif that helps visualize sine and cosine.
98 |
99 | 
100 |
101 | And here's an image to show what exactly is going on in the gif.
102 |
103 | 
104 |
105 | Sine and cosine are a number between -1 and 1 based on the angle.
106 |
107 | If the angle would point to the left then cosine would be -1 and sine would be 0.
108 |
109 | 
110 |
111 | If the angle would point down then cosine would be 0 and sine would be 1.
112 |
113 | 
114 |
115 | So how can we use these values to make our circle move towards the mouse? Well by multiplying our speed with them. For example, if the mouse is at a diagonal angle, let's say upper right, sine would be something like -0.7 and cosine would be 0.7.
116 |
117 | Now if we were to do this:
118 |
119 | ```lua
120 | circle.x = circle.x + circle.speed * dt
121 | circle.y = circle.y + circle.speed * dt
122 | ```
123 |
124 | Our circle would move straight to the bottom-right. But multiplied with sine and cosine like this:
125 |
126 | ```lua
127 | circle.x = circle.x + circle.speed * cos * dt
128 | circle.y = circle.y + circle.speed * sin * dt
129 | ```
130 |
131 | Then our circle would move horizontally with `circle.speed * 0.7`
132 |
133 | And would move vertically with `circle.speed * -0.7`.
134 |
135 | Which means it should move straight towards our mouse. Try it out!
136 |
137 | ```lua
138 | function love.update(dt)
139 | --love.mouse.getPosition returns the x and y position of the cursor.
140 | mouse_x, mouse_y = love.mouse.getPosition()
141 |
142 | angle = math.atan2(mouse_y - circle.y, mouse_x - circle.x)
143 |
144 | cos = math.cos(angle)
145 | sin = math.sin(angle)
146 |
147 | --Make the circle move towards the mouse
148 | circle.x = circle.x + circle.speed * cos * dt
149 | circle.y = circle.y + circle.speed * sin * dt
150 | end
151 |
152 |
153 | function love.draw()
154 | love.graphics.circle("line", circle.x, circle.y, circle.radius)
155 |
156 |
157 | --The angle
158 | love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
159 | love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
160 | love.graphics.line(circle.x, circle.y, circle.x, mouse_y)
161 |
162 | end
163 | ```
164 |
165 | 
166 |
167 | ___
168 |
169 | ## Distance
170 |
171 | Now let's say we only want to move the circle when it's near our cursor. To do that we need to calculate the distance between them. For this we use the Pythagorean theorem.
172 |
173 | With the Pythagorean theorem you can calculate the longest line in a triangle with a right angle.
174 |
175 | 
176 |
177 | Basically what you do is you use the length of the shorter sides to make 2 squares. Then you sum up those squares to make one big square. And finally you find the root of the square and you get the length of the longest line, also known as the *hypotenuse*.
178 |
179 | So how does this help us with finding the distance? Well, when you have 2 points, in our case the circle and cursor, there is also an invisible triangle.
180 |
181 | Check it out:
182 |
183 | ```lua
184 | function love.draw()
185 | love.graphics.circle("line", circle.x, circle.y, circle.radius)
186 | love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
187 | love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
188 | love.graphics.line(mouse_x, mouse_y, mouse_x, circle.y)
189 | end
190 | ```
191 |
192 | 
193 |
194 | If we use the Pythagorean theorem on this triangle we can figure out its hypotenuse, and thus we know the distance between the 2 points.
195 |
196 | Let's create a new function for this. First we need to the horizontal and vertical sides.
197 |
198 | ```lua
199 | function getDistance(x1, y1, x2, y2)
200 | local horizontal_distance = x1 - x2
201 | local vertical_distance = y1 - y2
202 | end
203 | ```
204 |
205 | Next we need to square these numbers. We can do this by multiplying it by itself or with `^2`.
206 |
207 | ```lua
208 | function getDistance(x1, y1, x2, y2)
209 | local horizontal_distance = x1 - x2
210 | local vertical_distance = y1 - y2
211 |
212 | --Both of these work
213 | local a = horizontal_distance * horizontal_distance
214 | local b = vertical_distance ^2
215 | end
216 | ```
217 |
218 | Now we need to sum these numbers and get the *square root*. If we square 5, so we do 5*5, or 5^2, we get 25 So the square root of 25 is 5. We can get the square root with `math.sqrt`.
219 |
220 | ```lua
221 | function getDistance(x1, y1, x2, y2)
222 | local horizontal_distance = x1 - x2
223 | local vertical_distance = y1 - y2
224 | --Both of these work
225 | local a = horizontal_distance * horizontal_distance
226 | local b = vertical_distance ^2
227 |
228 | local c = a + b
229 | local distance = math.sqrt(c)
230 | return distance
231 | end
232 | ```
233 |
234 | To proof that it works let's draw a circle with a radius of the distance.
235 |
236 | ```lua
237 | function love.draw()
238 | love.graphics.circle("line", circle.x, circle.y, circle.radius)
239 | love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
240 | love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
241 | love.graphics.line(mouse_x, mouse_y, mouse_x, circle.y)
242 |
243 | local distance = getDistance(circle.x, circle.y, mouse_x, mouse_y)
244 | love.graphics.circle("line", circle.x, circle.y, distance)
245 | end
246 | ```
247 |
248 | 
249 |
250 | It works! Now let's have some fun with it. I want the circle to only move when it's closer than 400 pixels, and the closer it gets the slower it moves.
251 |
252 | ```lua
253 | function love.update(dt)
254 | mouse_x, mouse_y = love.mouse.getPosition()
255 | angle = math.atan2(mouse_y - circle.y, mouse_x - circle.x)
256 | cos = math.cos(angle)
257 | sin = math.sin(angle)
258 |
259 | local distance = getDistance(circle.x, circle.y, mouse_x, mouse_y)
260 |
261 | if distance < 400 then
262 | circle.x = circle.x + circle.speed * cos * (distance/100) * dt
263 | circle.y = circle.y + circle.speed * sin * (distance/100) * dt
264 | end
265 | end
266 | ```
267 | 
268 |
269 | ___
270 |
271 | ## Image
272 | Let's use an image and make it look at the cursor.
273 |
274 | 
275 |
276 | The option argument for rotation is by default 0.
277 |
278 | With an angle of 0, cosine is 1 and sine is 0, meaning that the object would move to the right.
279 |
280 | So when you use an image, you should have it look to the right by default.
281 |
282 | ```lua
283 | function love.load()
284 | arrow = {}
285 | arrow.x = 200
286 | arrow.y = 200
287 | arrow.speed = 300
288 | arrow.angle = 0
289 | arrow.image = love.graphics.newImage("arrow_right.png")
290 | end
291 |
292 | function love.update(dt)
293 | mouse_x, mouse_y = love.mouse.getPosition()
294 | arrow.angle = math.atan2(mouse_y - arrow.y, mouse_x - arrow.x)
295 | cos = math.cos(arrow.angle)
296 | sin = math.sin(arrow.angle)
297 |
298 | arrow.x = arrow.x + arrow.speed * cos * dt
299 | arrow.y = arrow.y + arrow.speed * sin * dt
300 | end
301 |
302 | function love.draw()
303 | love.graphics.draw(arrow.image, arrow.x, arrow.y, arrow.angle)
304 | love.graphics.circle("fill", mouse_x, mouse_y, 5)
305 | end
306 | ```
307 |
308 | When you run the game you might notice that the arrow is a bit off.
309 |
310 | 
311 |
312 | This is because the image is rotating around its top-left, instead of its center. To fix this we need to put the origin in the center of the image.
313 |
314 |
315 | ```lua
316 | function love.load()
317 | arrow = {}
318 | arrow.x = 200
319 | arrow.y = 200
320 | arrow.speed = 300
321 | arrow.angle = 0
322 | arrow.image = love.graphics.newImage("arrow_right.png")
323 | arrow.origin_x = arrow.image:getWidth() / 2
324 | arrow.origin_y = arrow.image:getHeight() / 2
325 | end
326 | ```
327 |
328 | ```lua
329 | function love.draw()
330 | love.graphics.draw(arrow.image,
331 | arrow.x, arrow.y, arrow.angle, 1, 1,
332 | arrow.origin_x, arrow.origin_y)
333 | love.graphics.circle("fill", mouse_x, mouse_y, 5)
334 | end
335 | ```
336 | And now it correctly points to our cursor
337 |
338 | 
339 |
340 | ___
341 |
342 | ## Summary
343 |
344 | We can make an object move at an angle by getting the cosine and sine of the angle. Next we move with the x with a speed multiplied by the cosine, and y with the speed multiplied by sine. We can calculate the distance between two points by using the Pythagorean theorem. When using an image you should have it point to the right by default. Don't forget to put its origin in the center.
--------------------------------------------------------------------------------
/book/chapter17.md:
--------------------------------------------------------------------------------
1 | # Chapter 17 - Animation
2 |
3 | ## Frames
4 |
5 | Let's make an animated image.
6 |
7 | First, you'll need some images:
8 |
9 | , , ,  and 
10 |
11 | You can also download them as a zip [here](https://drive.google.com/file/d/1bMVMUv-B0XF9nBJfw_7ek781cmUjrdgn/view?usp=sharing)
12 |
13 | Load the images and put them in a table.
14 |
15 | ```lua
16 | function love.load()
17 | frames = {}
18 |
19 | table.insert(frames, love.graphics.newImage("jump1.png"))
20 | table.insert(frames, love.graphics.newImage("jump2.png"))
21 | table.insert(frames, love.graphics.newImage("jump3.png"))
22 | table.insert(frames, love.graphics.newImage("jump4.png"))
23 | table.insert(frames, love.graphics.newImage("jump5.png"))
24 | end
25 | ```
26 |
27 | Hold on, we can do this much more efficiently.
28 |
29 | ```lua
30 | function love.load()
31 | frames = {}
32 |
33 | for i=1,5 do
34 | table.insert(frames, love.graphics.newImage("jump" .. i .. ".png"))
35 | end
36 | end
37 | ```
38 |
39 | That's better! Now we need to create an animation. How will we do that?
40 |
41 | *A for-loop?*
42 |
43 | Nope. A for-loop would let us draw all the frames at the same time, but we want to draw a different frame every second. We need a variable that increases by 1 every second. Well that's easy!
44 |
45 | ```lua
46 | function love.load()
47 | frames = {}
48 |
49 | for i=1,5 do
50 | table.insert(frames, love.graphics.newImage("jump" .. i .. ".png"))
51 | end
52 |
53 | --I use a long name to avoid confusion with the variable named frames
54 | currentFrame = 1
55 | end
56 |
57 |
58 | function love.update(dt)
59 | currentFrame = currentFrame + dt
60 | end
61 | ```
62 |
63 | Now we have the variable `currentFrame` which increases by 1 every second, let's use this variable to draw the frames.
64 |
65 | ```lua
66 | function love.draw()
67 | love.graphics.draw(frames[currentFrame])
68 | end
69 | ```
70 |
71 | If you run the game you'll get an error: *bad argument #1 to 'draw' (Drawable expected, got nil)*
72 |
73 | This is because our variable `currentFrame` has decimals. After the first update `currentFrame` is something like 1.016, and while our table has something on position 1 and 2, there is nothing on position 1.016.
74 |
75 | To solve this we round the number down with `math.floor`. So 1.016 will become 1.
76 |
77 | ```lua
78 | function love.draw()
79 | love.graphics.draw(frames[math.floor(currentFrame)])
80 | end
81 | ```
82 |
83 | Run the game and you'll see that our animation works, but you'll eventually get an error. This is because currentFrame became higher than (or equal to) 6. And we only have 5 frames. To solve this, we reset `currentFrame` if it gets higher than (or equal to) 6. And while we're at it, let's speed up our animation.
84 |
85 | ```lua
86 | function love.update(dt)
87 | currentFrame = currentFrame + 10 * dt
88 | if currentFrame >= 6 then
89 | currentFrame = 1
90 | end
91 | end
92 | ```
93 |
94 | Look at him go!
95 |
96 | 
97 |
98 | ___
99 |
100 | ## Quads
101 |
102 | So this works, but it's not very efficient. With large animations we're going to need a lot of images. What if we put all the frames into 1 image, and then draw part of the image. We can do this with quads.
103 |
104 | First, download this image:
105 |
106 | 
107 |
108 | We'll remake the function `love.load` (you can keep `love.update` and `love.draw` the way it is).
109 |
110 | ```lua
111 | function love.load()
112 | image = love.graphics.newImage("jump.png")
113 | end
114 | ```
115 |
116 | Imagine quads like a rectangle that we cut out of our image. We tell the game "We want this part of the image". We're going to make a quad of the first frame. You can make a quad with `love.graphics.newQuad` [(wiki)](https://love2d.org/wiki/love.graphics.newQuad).
117 |
118 | The first arguments are the x and y position of our quad. Well since we want the first frame we take the upper-left corner of our image, so 0,0.
119 |
120 | ```lua
121 | function love.load()
122 | image = love.graphics.newImage("jump.png")
123 | frames = {}
124 | table.insert(frames, love.graphics.newQuad(0, 0))
125 | end
126 | ```
127 |
128 | Again, quads are like cutting a piece of paper. Where we will eventually draw our image is not relevant to the quad.
129 |
130 | 
131 |
132 | Next 2 arguments are the width and height of our quad. The width of a frame in our image is 117 and the height is 233. The last 2 arguments are the width and height of the full image. We can get these with `image:getWidth()` and `image:getHeight()`.
133 |
134 | ```lua
135 | function love.load()
136 | image = love.graphics.newImage("jump.png")
137 | frames = {}
138 | local frame_width = 117
139 | local frame_height = 233
140 | table.insert(frames, love.graphics.newQuad(0, 0, frame_width, frame_height, image:getWidth(), image:getHeight()))
141 |
142 | currentFrame = 1
143 | end
144 | ```
145 |
146 | Now let's test our quad by drawing it. You draw a quad by passing it as second argument in `love.graphics.draw`.
147 |
148 | ```lua
149 | function love.draw()
150 | love.graphics.draw(image, frames[1], 100, 100)
151 | end
152 | ```
153 |
154 | As you can see it's drawing our first frame. Great, now let's make the second quad.
155 |
156 | To draw the second frame all we need to do is move the rectangle to the right. Since each frame has a width of 117, all we need to do is move our x to the right with 117.
157 |
158 | ```lua
159 | function love.load()
160 | image = love.graphics.newImage("jump.png")
161 | frames = {}
162 | local frame_width = 117
163 | local frame_height = 233
164 | table.insert(frames, love.graphics.newQuad(0, 0, frame_width, frame_height, image:getWidth(), image:getHeight()))
165 | table.insert(frames, love.graphics.newQuad(frame_width, 0, frame_width, frame_height, image:getWidth(), image:getHeight()))
166 | end
167 | ```
168 |
169 | And we can do the same for the 3rd quad.
170 |
171 | 
172 |
173 | Wait, are we repeating the same action? Don't we have something for that? For-loops! Also, we can prevent calling :getWidth and :getHeight multiple times by storing the values in a variable.
174 |
175 | ```lua
176 | function love.load()
177 | image = love.graphics.newImage("jump.png")
178 | local width = image:getWidth()
179 | local height = image:getHeight()
180 |
181 | frames = {}
182 |
183 | local frame_width = 117
184 | local frame_height = 233
185 |
186 | for i=0,4 do
187 | table.insert(frames, love.graphics.newQuad(i * frame_width, 0, frame_width, frame_height, width, height))
188 | end
189 |
190 | currentFrame = 1
191 | end
192 | ```
193 |
194 | Notice how we start our for-loop on 0 and end it on 4, instead of 1 to 5. This is because our first quad is on position 0, and 0 * 177 equals 0.
195 |
196 | Now all that is left to do is use currentFrame for the quad we want to draw.
197 |
198 | ```lua
199 | function love.draw()
200 | love.graphics.draw(image, frames[math.floor(currentFrame)], 100, 100)
201 | end
202 | ```
203 |
204 | ___
205 |
206 | ## Multiple rows
207 |
208 | So we can now turn a row of frames into an animation, but what if we have multiple rows?
209 |
210 | 
211 |
212 | Easy, we just have to repeat the same thing with a different y value.
213 |
214 | ```lua
215 | function love.load()
216 | image = love.graphics.newImage("jump_2.png")
217 | local width = image:getWidth()
218 | local height = image:getHeight()
219 |
220 | frames = {}
221 |
222 | local frame_width = 117
223 | local frame_height = 233
224 |
225 | for i=0,2 do
226 | table.insert(frames, love.graphics.newQuad(i * frame_width, 0, frame_width, frame_height, width, height))
227 | end
228 |
229 | for i=0,1 do
230 | table.insert(frames, love.graphics.newQuad(i * frame_width, frame_height, frame_width, frame_height, width, height))
231 | end
232 |
233 | currentFrame = 1
234 | end
235 | ```
236 |
237 | But wait, there it is again: Repetition! And what do we do when we see repetition? We use a for-loop.
238 |
239 | *So what, like, a for-loop inside a for-loop?*
240 |
241 | Exactly! We're going to have to make a few changes though.
242 |
243 | ```lua
244 | function love.load()
245 | image = love.graphics.newImage("jump_2.png")
246 | local width = image:getWidth()
247 | local height = image:getHeight()
248 |
249 | frames = {}
250 |
251 | local frame_width = 117
252 | local frame_height = 233
253 |
254 | for i=0,1 do
255 | --I changed i to j in the inner for-loop
256 | for j=0,2 do
257 | --Meaning you also need to change it here
258 | table.insert(frames, love.graphics.newQuad(j * frame_width, i * frame_height, frame_width, frame_height, width, height))
259 | end
260 | end
261 |
262 | currentFrame = 1
263 | end
264 | ```
265 |
266 | So in the first iteration of the outer for-loop, i equals 0, and j equals 0, then 1 and finally 2.
267 | In the second iteration, i equals 1, and j again equals 0, then 1 and finally 2.
268 |
269 | You might notice that we have an extra, empty quad. This isn't really a big deal since we only draw the first 5 quads, but we can do something like this to prevent it
270 |
271 | ```lua
272 | maxFrames = 5
273 | for i=0,1 do
274 | for j=0,2 do
275 | table.insert(frames, love.graphics.newQuad(j * frame_width, i * frame_height, frame_width, frame_height, width, height))
276 | if #frames == maxFrames then
277 | break
278 | end
279 | end
280 | print("I don't break!")
281 | end
282 | ```
283 |
284 | With `break` we can end a for-loop. This will prevent it from adding that last quad.
285 |
286 | Note how *"I don't break"* gets printed. This is because `break` only breaks the loop you use it in, the outer loop still continues. It could be fixed by adding the same if-statement in our outer-loop, but in our case it doesn't matter since the loop at that point is already on its last iteration.
287 |
288 | ___
289 |
290 | ## Bleeding
291 |
292 | When rotating and/or scaling an image while using quads, an effect can appear called *bleeding*. What happens is that part of the image outside of the quad gets drawn.
293 |
294 | So if this was our spritesheet:
295 |
296 | 
297 |
298 | Our first frame could end up like this:
299 |
300 | 
301 |
302 | It's kind of technical why this happens, but the fact is that it does happen. Luckily, we can solve this issue by adding a 1 pixel border around our frame.
303 | Either of the same color as the actually border, or with transparency.
304 |
305 | 
306 |
307 | And then we don't include that border inside the quad.
308 |
309 | I added a border to our jumping character. Instead of transparent I made it purple so that we can see if we're not accidentally drawing part of the border.
310 |
311 | 
312 |
313 | Let's do this step by step.
314 |
315 | First, we do not want the first pixel to be drawn, so our quad starts at 1 (instead of 0).
316 |
317 | ```lua
318 | newQuad(1, 1, frame_width, frame_height, width, height)
319 | ```
320 |
321 | Okay so this works for the first frame, but what part of the next frame do we want to draw? Simply add the frame width/height?
322 |
323 |
324 | ```lua
325 | newQuad(1 + j * frame_width, 1 + i * frame_height, frame_width, frame_height, width, height)
326 | ```
327 |
328 | Almost. We're missing something.
329 |
330 | 
331 |
332 | The blue line is our quad. As you can see, the quad is 2 pixels to the left of where it's supposed to be. So let's add 2 to how much we move each iteration.
333 |
334 | ```lua
335 | newQuad(1 + j * (frame_width + 2), 1 + i * (frame_height + 2), frame_width, frame_height, width, height)
336 | ```
337 |
338 | And now our quads are in the correct position. Here's an image visualizing how we position the quad. So we add 1, then add `frame_width` + 2, multiplied by `i`. This way we position the quad correctly for each frame.
339 |
340 | 
341 |
342 | ___
343 |
344 | ## Summary
345 |
346 | With quads we can draw part of an image. we can use this to turn a spritesheet into an animation. In case of multiple rows we can use a for-loop inside a for-loop to cover the whole sheet. We can use `break` to end a loop. We add a 1 pixel border to our sprites to prevent *bleeding*.
347 |
--------------------------------------------------------------------------------
/book/chapter14.md:
--------------------------------------------------------------------------------
1 | # Chapter 14 - Game: Shoot the enemy
2 |
3 | Let's use everything we learned so far to create a simple game. You can read about programming and making games all you want, but to really learn it you'll have to do it.
4 |
5 | A game is essentially a bunch of problems that you have to solve. When you ask an experienced programmer to make PONG, he won't look up a *How to make PONG*. They can divide PONG into separate problems, and know how to solve each one of them. This chapter is to show you how to split a game into multiple tasks.
6 |
7 | The game we'll be making is simple: An enemy is bouncing against the walls. We have to shoot it. Each time we shoot it, the enemy goes a little faster. When you miss, it's game over and you'll have to start over again.
8 |
9 | 
10 |
11 | For this game we're going to use images. You're free to use your own images, but I'm going to use these 3:
12 |
13 | 
14 | 
15 | 
16 |
17 | These images are made by [Kenney](http://kenney.nl/assets), who makes a lot of free assets that anyone can use in their games. Check him out!
18 |
19 | Let's start with the 3 main callbacks, and load *classic*, the library we use to simulate classes.
20 |
21 | ```lua
22 | function love.load()
23 | Object = require "classic"
24 | end
25 |
26 | function love.update(dt)
27 |
28 | end
29 |
30 | function love.draw()
31 |
32 | end
33 | ```
34 |
35 | Let's start with the player. Create a new file called `player.lua`.
36 |
37 | We could make a base class for all our objects, but because it's such a short simple game we'll do without one. Though I encourage you to improve the code at the end of this chapter by adding a base class yourself.
38 |
39 | ___
40 |
41 | ## Task: Create a moving player
42 |
43 | Create a Player class:
44 |
45 | ```lua
46 | --! file: player.lua
47 | Player = Object:extend()
48 |
49 | function Player:new()
50 |
51 | end
52 | ```
53 |
54 | I'm going to give the panda image to my player.
55 |
56 |
57 | ```lua
58 | function Player:new()
59 | self.image = love.graphics.newImage("panda.png")
60 | end
61 |
62 |
63 | function Player:draw()
64 | love.graphics.draw(self.image)
65 | end
66 | ```
67 |
68 | Next let's make it possible to move our player with our arrow keys.
69 |
70 | ```lua
71 | function Player:new()
72 | self.image = love.graphics.newImage("panda.png")
73 | self.x = 300
74 | self.y = 20
75 | self.speed = 500
76 | self.width = self.image:getWidth()
77 | end
78 |
79 | function Player:update(dt)
80 | if love.keyboard.isDown("left") then
81 | self.x = self.x - self.speed * dt
82 | elseif love.keyboard.isDown("right") then
83 | self.x = self.x + self.speed * dt
84 | end
85 | end
86 |
87 | function Player:draw()
88 | love.graphics.draw(self.image, self.x, self.y)
89 | end
90 | ```
91 |
92 | And now we should be able to move our player. Let's go back to `main.lua` and load our player.
93 |
94 | ```lua
95 | --! file: main.lua
96 | function love.load()
97 | Object = require "classic"
98 | require "player"
99 |
100 | player = Player()
101 | end
102 |
103 | function love.update(dt)
104 | player:update(dt)
105 | end
106 |
107 | function love.draw()
108 | player:draw()
109 | end
110 | ```
111 |
112 | As you can see, we can move our player. But our player can move out of the window. Let's fix this with if-statements.
113 |
114 | ```lua
115 | --! file: player.lua
116 |
117 | function Player:update(dt)
118 | if love.keyboard.isDown("left") then
119 | self.x = self.x - self.speed * dt
120 | elseif love.keyboard.isDown("right") then
121 | self.x = self.x + self.speed * dt
122 | end
123 |
124 | --Get the width of the window
125 | local window_width = love.graphics.getWidth()
126 |
127 | --If the x is too far too the left then..
128 | if self.x < 0 then
129 | --Set x to 0
130 | self.x = 0
131 |
132 | --Else, if the x is too far to the right then..
133 | elseif self.x > window_width then
134 | --Set the x to the window's width.
135 | self.x = window_width
136 | end
137 | end
138 | ```
139 |
140 | Oops, our player can still move too far to the right. We need to include our width when checking if we're hitting the right wall.
141 |
142 | ```lua
143 | --If the left side is too far too the left then..
144 | if self.x < 0 then
145 | --Set x to 0
146 | self.x = 0
147 |
148 | --Else, if the right side is too far to the right then..
149 | elseif self.x + self.width > window_width then
150 | --Set the right side to the window's width.
151 | self.x = window_width - self.width
152 | end
153 | ```
154 |
155 | And now it's fixed. Our player can't move out of the window anymore.
156 |
157 | ___
158 |
159 | ## Task: Create a moving enemy
160 |
161 | Now let's make the Enemy class. Create a new file called `enemy.lua`, and type the following:
162 |
163 | ```lua
164 | --! file: enemy.lua
165 | Enemy = Object:extend()
166 |
167 | function Enemy:new()
168 |
169 |
170 | end
171 | ```
172 |
173 | I'm going to give the enemy the snake image, and make it move by itself.
174 |
175 |
176 | ```lua
177 | function Enemy:new()
178 | self.image = love.graphics.newImage("snake.png")
179 | self.x = 325
180 | self.y = 450
181 | self.speed = 100
182 | end
183 |
184 | function Enemy:update(dt)
185 | self.x = self.x + self.speed * dt
186 | end
187 |
188 | function Enemy:draw()
189 | love.graphics.draw(self.image, self.x, self.y)
190 | end
191 | ```
192 |
193 | We need to make the enemy bounce against the walls, but let's load it first.
194 |
195 | ```lua
196 | --! file: main.lua
197 | function love.load()
198 | Object = require "classic"
199 | require "player"
200 | require "enemy"
201 |
202 | player = Player()
203 | enemy = Enemy()
204 | end
205 |
206 | function love.update(dt)
207 | player:update(dt)
208 | enemy:update(dt)
209 | end
210 |
211 | function love.draw()
212 | player:draw()
213 | enemy:draw()
214 | end
215 | ```
216 |
217 | Okay now we can see the enemy move, and we can see it move out of our window. Let's make sure it doesn't move out of our window like with Player.
218 |
219 | ```lua
220 | function Enemy:new()
221 | self.image = love.graphics.newImage("snake.png")
222 | self.x = 325
223 | self.y = 450
224 | self.speed = 100
225 | self.width = self.image:getWidth()
226 | self.height = self.image:getHeight()
227 | end
228 |
229 | function Enemy:update(dt)
230 | self.x = self.x + self.speed * dt
231 |
232 | local window_width = love.graphics.getWidth()
233 |
234 | if self.x < 0 then
235 | self.x = 0
236 | elseif self.x + self.width > window_width then
237 | self.x = window_width - self.width
238 | end
239 | end
240 | ```
241 |
242 | Our enemy stops at the wall, but we want to make it bounce. How are we going to make it do that? It hits the right wall, and then what? It should move to the other direction. How do we make it move to the other direction? By changing the value of `speed`. And what should the value of `speed` become? It shouldn't be 100 but -100.
243 |
244 | So should we do `self.speed = -100`? Well no. Because like I said before we'll make the enemy speed up as it gets hit, and this way it would reset its speed when it bounces. Instead, we should invert the value of `speed`. So `speed` becomes `-speed`. In other words, if the speed were to be increased to 120, it would then become -120.
245 |
246 | And what if it hits the left wall? At that point speed is a negative number, and we should turn it into a positive number. How can we do that? Well, [negative times negative makes positive](https://www.khanacademy.org/math/algebra-basics/basic-alg-foundations/alg-basics-negative-numbers/v/why-a-negative-times-a-negative-is-a-positive). So if we say that `speed`, which at that point is a negative number, becomes `-speed`, it will turn into a positive number.
247 |
248 | ```lua
249 | function Enemy:update(dt)
250 | self.x = self.x + self.speed * dt
251 |
252 | local window_width = love.graphics.getWidth()
253 |
254 | if self.x < 0 then
255 | self.x = 0
256 | self.speed = -self.speed
257 | elseif self.x + self.width > window_width then
258 | self.x = window_width - self.width
259 | self.speed = -self.speed
260 | end
261 | end
262 | ```
263 |
264 | Alright we got a player and a moving enemy, now all that's left is the bullet.
265 |
266 | ___
267 |
268 | ## Task: Be able to shoot bullets
269 |
270 | Create a new file called `bullet.lua`, and write the following code:
271 |
272 | ```lua
273 | --! file: bullet.lua
274 |
275 | Bullet = Object:extend()
276 |
277 | function Bullet:new()
278 | self.image = love.graphics.newImage("bullet.png")
279 | end
280 |
281 | function Bullet:draw()
282 | love.graphics.draw(self.image)
283 | end
284 | ```
285 |
286 | The bullets should move vertical instead of horizontal.
287 |
288 | ```lua
289 | --We pass the x and y of the player.
290 | function Bullet:new(x, y)
291 | self.image = love.graphics.newImage("bullet.png")
292 | self.x = x
293 | self.y = y
294 | self.speed = 700
295 | --We'll need these for collision checking
296 | self.width = self.image:getWidth()
297 | self.height = self.image:getHeight()
298 | end
299 |
300 | function Bullet:update(dt)
301 | self.y = self.y + self.speed * dt
302 | end
303 |
304 | function Bullet:draw()
305 | love.graphics.draw(self.image, self.x, self.y)
306 | end
307 | ```
308 |
309 | Now we need to be able to shoot bullets. In main.lua load the file and create a table.
310 |
311 | ```lua
312 | --! file: main.lua
313 | function love.load()
314 | Object = require "classic"
315 | require "player"
316 | require "enemy"
317 | require "bullet"
318 |
319 | player = Player()
320 | enemy = Enemy()
321 | listOfBullets = {}
322 | end
323 | ```
324 |
325 | Now we give Player a function that creates a bullet when space is pressed.
326 |
327 | ```lua
328 | --! file: player.lua
329 | function Player:keyPressed(key)
330 | --If the spacebar is pressed
331 | if key == "space" then
332 | --Put a new instance of Bullet inside listOfBullets.
333 | table.insert(listOfBullets, Bullet(self.x, self.y))
334 | end
335 | end
336 | ```
337 |
338 | And we need to call this function in the `love.keypressed` callback.
339 |
340 | ```lua
341 | --! file: main.lua
342 | function love.keypressed(key)
343 | player:keyPressed(key)
344 | end
345 | ```
346 |
347 | And now we need to iterate through the table and update/draw all the bullets.
348 |
349 | ```
350 | function love.load()
351 | Object = require "classic"
352 | require "player"
353 | require "enemy"
354 | require "bullet"
355 |
356 | player = Player()
357 | enemy = Enemy()
358 | listOfBullets = {}
359 | end
360 |
361 | function love.update(dt)
362 | player:update(dt)
363 | enemy:update(dt)
364 |
365 | for i,v in ipairs(listOfBullets) do
366 | v:update(dt)
367 | end
368 | end
369 |
370 | function love.draw()
371 | player:draw()
372 | enemy:draw()
373 |
374 | for i,v in ipairs(listOfBullets) do
375 | v:draw()
376 | end
377 | end
378 | ```
379 | Awesome, our player can now shoot bullets.
380 |
381 | ___
382 |
383 | ## Task: Make bullets affect the enemy's speed
384 |
385 | Now we need to make it so that the snake can get hit by the bullet. We give Bullet a collision detection function.
386 |
387 | ```lua
388 | --! file: bullet.lua
389 | function Bullet:checkCollision(obj)
390 |
391 | end
392 | ```
393 |
394 | Do you still know how to do it? Do you still know the 4 conditions that need to be true to assure collision is happening?
395 |
396 | Instead of returning true and false, we increase the enemy's speed. We give the property `dead` to the bullet, which we'll use to remove it from the list.
397 |
398 | ```lua
399 | function Bullet:checkCollision(obj)
400 | local self_left = self.x
401 | local self_right = self.x + self.width
402 | local self_top = self.y
403 | local self_bottom = self.y + self.height
404 |
405 | local obj_left = obj.x
406 | local obj_right = obj.x + obj.width
407 | local obj_top = obj.y
408 | local obj_bottom = obj.y + obj.height
409 |
410 | if self_right > obj_left
411 | and self_left < obj_right
412 | and self_bottom > obj_top
413 | and self_top < obj_bottom then
414 | self.dead = true
415 |
416 | --Increase enemy speed
417 | obj.speed = obj.speed + 50
418 | end
419 | end
420 | ```
421 |
422 |
423 | Now we need to call checkCollision in main.lua.
424 |
425 | ```lua
426 | function love.update(dt)
427 | player:update(dt)
428 | enemy:update(dt)
429 |
430 | for i,v in ipairs(listOfBullets) do
431 | v:update(dt)
432 |
433 | --Each bullets checks if there is collision with the enemy
434 | v:checkCollision(enemy)
435 | end
436 | end
437 | ```
438 |
439 | And next we need to destroy the bullets that are dead.
440 |
441 | ```lua
442 | function love.update(dt)
443 | player:update(dt)
444 | enemy:update(dt)
445 |
446 | for i,v in ipairs(listOfBullets) do
447 | v:update(dt)
448 | v:checkCollision(enemy)
449 |
450 | --If the bullet has the property dead and it's true then..
451 | if v.dead then
452 | --Remove it from the list
453 | table.remove(listOfBullets, i)
454 | end
455 | end
456 | end
457 | ```
458 |
459 | Last thing to do is to restart the game when we miss the enemy. We need to check if the bullet is out of the screen.
460 |
461 |
462 | ```lua
463 | --! file: bullet.lua
464 | function Bullet:update(dt)
465 | self.y = self.y + self.speed * dt
466 |
467 | --If the bullet is out of the screen
468 | if self.y > love.graphics.getHeight() then
469 | --Restart the game
470 | love.load()
471 | end
472 | end
473 | ```
474 |
475 | And let's test it out. You might notice that when you hit the enemy while it's moving to the left, it slows down. This is because the enemy's speed is at that point a negative number. So by increasing the number the enemy slows down. To fix this we need to check if the enemy's speed is a negative number or not.
476 |
477 | ```lua
478 | function Bullet:checkCollision(obj)
479 | local self_left = self.x
480 | local self_right = self.x + self.width
481 | local self_top = self.y
482 | local self_bottom = self.y + self.height
483 |
484 | local obj_left = obj.x
485 | local obj_right = obj.x + obj.width
486 | local obj_top = obj.y
487 | local obj_bottom = obj.y + obj.height
488 |
489 | if self_right > obj_left
490 | and self_left < obj_right
491 | and self_bottom > obj_top
492 | and self_top < obj_bottom then
493 | self.dead = true
494 |
495 | --Increase enemy speed
496 | if obj.speed > 0 then
497 | obj.speed = obj.speed + 50
498 | else
499 | obj.speed = obj.speed - 50
500 | end
501 | end
502 | end
503 | ```
504 |
505 | And with that our game is finished. Or is it? You should try adding features to the game yourself. Or make a whole new game. It doesn't matter as long as you keep learning and keep making games!
506 |
507 | ___
508 |
509 | ## Summary
510 | A game is essentially a bunch of problems that need to be solved.
511 |
--------------------------------------------------------------------------------
/book/chapter18.md:
--------------------------------------------------------------------------------
1 | # Chapter 18 - Tiles
2 |
3 | ___
4 |
5 | *Be sure to read the comments in the code, I put a lot of important information in there*
6 |
7 | ___
8 |
9 |
10 | In a lot of 2D games the levels are made out of tiles. We're going to make our own tiled level.
11 |
12 | Let's start off by creating a line. Create a table and fill it with ones and zeroes.
13 |
14 | ```lua
15 | function love.load()
16 | tilemap = {1, 0, 0, 1, 1, 0, 1, 1, 1, 0}
17 | end
18 | ```
19 |
20 | This is our level. A `1` is a tile and a `0` is empty. Now we need to draw it. We loop through the table, and every time we encounter a 1, we draw a rectangle on its position.
21 |
22 | ```lua
23 | function love.draw()
24 | --ipairs recap
25 | --ipairs is a special function that allows you to loop through a table
26 | --Every iteration i becomes what iteration the loop is at, so 1, 2, 3, 4, etc)
27 | --Every iteration v becomes the value on position i, so in our case 1, 0, 0, 1, 1, 0, etc.
28 | for i,v in ipairs(tilemap) do
29 | if v == 1 then
30 | love.graphics.rectangle("line", i * 25, 100, 25, 25)
31 | end
32 | end
33 | end
34 | ```
35 |
36 | 
37 |
38 | Okay so this works, but now we want to go vertical. We do this by putting tables inside a table, also known as a 2D table.
39 |
40 | ```lua
41 | function love.load()
42 | tilemap = {
43 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
44 | {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
45 | {1, 0, 0, 1, 1, 1, 1, 0, 0, 1},
46 | {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
47 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
48 | }
49 | end
50 | ```
51 |
52 | So now we have a table filled with tables. See it as an Excel table.
53 |
54 | 
55 |
56 | 1, 2, 3, etc. are what we call *rows* and A, B, C, etc. are called *columns*.
57 |
58 | Another way to look at it as a small town
59 |
60 | 
61 |
62 | Every row of houses is a table, and multiple rows make the whole town, or in our case our level.
63 |
64 | The green house is on the 2nd row on the 5th column.
65 |
66 | The red house is on the 3rd row on the 2nd column.
67 |
68 | With 2D tables we access the values like this:
69 |
70 | `tilemap[4][3]`
71 |
72 | This means: The 3rd value of the 4th table. Or: The 3rd column on the 4th row.
73 |
74 | 
75 |
76 | Let's draw our level. Because we have a 2D table, we need to use a for-loop inside a for-loop. This is also called a *nested for-loop*.
77 |
78 | ```lua
79 | function love.draw()
80 | --Let's do it without ipairs first.
81 |
82 | --For i=1 till the number of values in tilemap
83 | for i=1,#tilemap do
84 | --For j till the number of values in this row
85 | for j=1,#tilemap[i] do
86 | --If the value on row i, column j equals 1
87 | if tilemap[i][j] == 1 then
88 | --Draw the rectangle.
89 | --Use i and j to position the rectangle.
90 | -- j for x, i for y.
91 | love.graphics.rectangle("line", j * 25, i * 25, 25, 25)
92 | end
93 | end
94 | end
95 | end
96 | ```
97 |
98 | So we loop through our rows, and for every row we loop through our columns.
99 |
100 | We use `j`, of our inner for-loop, for our horizontal positioning and `i`, of our outer for-loop for the y positioning. Remember that these are just variable names and can be named whatever, but using i and j like this is common.
101 |
102 | Let's turn the for-loops into an ipairs loop.
103 |
104 | ```lua
105 | function love.draw()
106 | for i,row in ipairs(tilemap) do
107 | for j,tile in ipairs(row) do
108 | if tile == 1 then
109 | love.graphics.rectangle("line", j * 25, i * 25, 25, 25)
110 | end
111 | end
112 | end
113 | end
114 | ```
115 |
116 | We use the variable names `row` and `tile` to make it more clear what is going on. We loop through the table `tilemap`, and each value is a *row*. We loop through the row and each value is a *tile*.
117 |
118 | We can also use different numbers for our tiles, and use these numbers to give the tiles different colors.
119 |
120 | ```lua
121 | function love.load()
122 | tilemap = {
123 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
124 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
125 | {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
126 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
127 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
128 | }
129 | end
130 |
131 | function love.draw()
132 | for i,row in ipairs(tilemap) do
133 | for j,tile in ipairs(row) do
134 | --First check if the tile is not zero
135 | if tile ~= 0 then
136 |
137 | --Set the color based on the tile number
138 | if tile == 1 then
139 | --setColor uses RGB, A is optional
140 | --Red, Green, Blue, Alpha
141 | love.graphics.setColor(1, 1, 1)
142 | elseif tile == 2 then
143 | love.graphics.setColor(1, 0, 0)
144 | elseif tile == 3 then
145 | love.graphics.setColor(1, 0, 1)
146 | elseif tile == 4 then
147 | love.graphics.setColor(0, 0, 1)
148 | elseif tile == 5 then
149 | love.graphics.setColor(0, 1, 1)
150 | end
151 |
152 | --Draw the tile
153 | love.graphics.rectangle("fill", j * 25, i * 25, 25, 25)
154 | end
155 | end
156 | end
157 | end
158 | ```
159 |
160 | Or a better way to do this:
161 |
162 | ```lua
163 | function love.load()
164 | tilemap = {
165 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
166 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
167 | {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
168 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
169 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
170 | }
171 |
172 | --Create a table named colors
173 | colors = {
174 | --Fill it with tables filled with RGB numbers
175 | {1, 1, 1},
176 | {1, 0, 0},
177 | {1, 0, 1},
178 | {0, 0, 1},
179 | {0, 1, 1}
180 | }
181 | end
182 |
183 | function love.draw()
184 | for i,row in ipairs(tilemap) do
185 | for j,tile in ipairs(row) do
186 | --First check if the tile is not zero
187 | if tile ~= 0 then
188 | --Set the color. .setColor() also accepts a table with 3 numbers.
189 | --We pass the table the table whose position in colors matches the value of tile
190 | --So if tile equals 3 then we pass colors[3] which is {1, 0, 1}
191 | love.graphics.setColor(colors[tile])
192 | --Draw the tile
193 | love.graphics.rectangle("fill", j * 25, i * 25, 25, 25)
194 | end
195 | end
196 | end
197 | end
198 | ```
199 |
200 | 
201 |
202 | ## Images
203 |
204 | So we can make a colorful level, but now we want to use images. Well that's easy, just add an image, get the width and height, and draw the image instead of a rectangle.
205 |
206 | I will use this image:
207 |
208 | 
209 |
210 | ```lua
211 | function love.load()
212 |
213 | --Load the image
214 | image = love.graphics.newImage("tile.png")
215 |
216 | --Get the width and height
217 | width = image:getWidth()
218 | height = image:getHeight()
219 |
220 | tilemap = {
221 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
222 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
223 | {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
224 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
225 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
226 | }
227 |
228 | colors = {
229 | --Fill it with tables filled with RGB numbers
230 | {1, 1, 1},
231 | {1, 0, 0},
232 | {1, 0, 1},
233 | {0, 0, 1},
234 | {0, 1, 1}
235 | }
236 | end
237 |
238 | function love.draw()
239 | for i,row in ipairs(tilemap) do
240 | for j,tile in ipairs(row) do
241 | if tile ~= 0 then
242 | love.graphics.setColor(colors[tile])
243 | --Draw the image
244 | love.graphics.draw(image, j * width, i * height)
245 | end
246 | end
247 | end
248 | end
249 | ```
250 |
251 | 
252 |
253 | So that's easy. But what if we want to draw different images? Well we could use multiple images, but in the previous chapter we learned how we can draw part of an image with quads. We can use this for tiles as well.
254 |
255 | Let's use this tileset:
256 |
257 | 
258 |
259 | First we need to create the quads.
260 |
261 | ```lua
262 | function love.load()
263 |
264 | --Load the image
265 | image = love.graphics.newImage("tileset.png")
266 |
267 | --We need the full image width and height for creating the quads
268 | local image_width = image:getWidth()
269 | local image_height = image:getHeight()
270 |
271 | --The width and height of each tile is 32, 32
272 | --So we could do:
273 | width = 32
274 | height = 32
275 | --But let's say we don't know the width and height of a tile
276 | --We can also use the number of rows and columns in the tileset
277 | --Our tileset has 2 rows and 3 columns
278 | --But we need to subtract 2 to make up for the empty pixels we included to prevent bleeding
279 | width = (image_width / 3) - 2
280 | height = (image_height / 2) - 2
281 |
282 | --Create the quads
283 | quads = {}
284 |
285 | for i=0,1 do
286 | for j=0,2 do
287 | --The only reason this code is split up in multiple lines
288 | --is so that it fits the page
289 | table.insert(quads,
290 | love.graphics.newQuad(
291 | 1 + j * (width + 2),
292 | 1 + i * (height + 2),
293 | width, height,
294 | image_width, image_height))
295 | end
296 | end
297 |
298 | tilemap = {
299 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
300 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
301 | {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
302 | {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
303 | {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
304 | }
305 |
306 | end
307 | ```
308 |
309 | Now that we have a table with quads, we put the number in our tilemap based on which quad we want. Based on the order we created our quads, they are on this position in our table:
310 |
311 | 
312 |
313 | So if we wanted to create this:
314 |
315 | 
316 |
317 | We would make our tilemap look like this:
318 |
319 | ```lua
320 | tilemap = {
321 | {1, 6, 6, 2, 1, 6, 6, 2},
322 | {3, 0, 0, 4, 5, 0, 0, 3},
323 | {3, 0, 0, 0, 0, 0, 0, 3},
324 | {4, 2, 0, 0, 0, 0, 1, 5},
325 | {1, 5, 0, 0, 0, 0, 4, 2},
326 | {3, 0, 0, 0, 0, 0, 0, 3},
327 | {3, 0, 0, 1, 2, 0, 0, 3},
328 | {4, 6, 6, 5, 4, 6, 6, 5}
329 | }
330 | ```
331 |
332 | If you compare the tilemap with the image and the numbers you can see how each tile is used.
333 |
334 | Now we need to draw the correct quad.
335 |
336 | ```lua
337 | function love.draw()
338 | for i,row in ipairs(tilemap) do
339 | for j,tile in ipairs(row) do
340 | if tile ~= 0 then
341 | --Draw the image with the correct quad
342 | love.graphics.draw(image, quads[tile], j * width, i * height)
343 | end
344 | end
345 | end
346 | end
347 | ```
348 |
349 | So on (1,1) we draw the quad on position 1. On (1,2) we draw the quad on position 6, etc.
350 |
351 | If you run the game you'll see that our level now looks like the image above.
352 |
353 |
354 | ## Player
355 |
356 | Now that we have a level, let's create a player that can walk around, but not go through walls.
357 |
358 | I will use this image for the player:
359 |
360 | 
361 |
362 | ```lua
363 | function love.load()
364 | image = love.graphics.newImage("tileset.png")
365 |
366 | local image_width = image:getWidth()
367 | local image_height = image:getHeight()
368 | width = (image_width / 3) - 2
369 | height = (image_height / 2) - 2
370 |
371 | quads = {}
372 |
373 | for i=0,1 do
374 | for j=0,2 do
375 | table.insert(quads,
376 | love.graphics.newQuad(
377 | 1 + j * (width + 2),
378 | 1 + i * (height + 2),
379 | width, height,
380 | image_width, image_height))
381 | end
382 | end
383 |
384 | tilemap = {
385 | {1, 6, 6, 2, 1, 6, 6, 2},
386 | {3, 0, 0, 4, 5, 0, 0, 3},
387 | {3, 0, 0, 0, 0, 0, 0, 3},
388 | {4, 2, 0, 0, 0, 0, 1, 5},
389 | {1, 5, 0, 0, 0, 0, 4, 2},
390 | {3, 0, 0, 0, 0, 0, 0, 3},
391 | {3, 0, 0, 1, 2, 0, 0, 3},
392 | {4, 6, 6, 5, 4, 6, 6, 5}
393 | }
394 |
395 | --Create our player
396 | player = {
397 | image = love.graphics.newImage("player.png"),
398 | tile_x = 2,
399 | tile_y = 2
400 | }
401 | end
402 | ```
403 |
404 | The `tile_x` and `tile_y` is the player's position on our tilemap. This number will be multiplied by the tile width and height when drawn. But first let's make it move. Instead of smooth movement we will make it jump to its next position, so we won't be needing `dt` for the movement. This also means that we don't want to know if the movement keys are *down*, but if they are *pressed*. For this we use the `love.keypressed` event.
405 |
406 | First we create local x and y variable. Next we add or subtract 1 to this variable based on the key that was pressed, and finally we assign this value to the player's position.
407 |
408 |
409 | ```lua
410 | function love.keypressed(key)
411 | local x = player.tile_x
412 | local y = player.tile_y
413 |
414 | if key == "left" then
415 | x = x - 1
416 | elseif key == "right" then
417 | x = x + 1
418 | elseif key == "up" then
419 | y = y - 1
420 | elseif key == "down" then
421 | y = y + 1
422 | end
423 |
424 | player.tile_x = x
425 | player.tile_y = y
426 | end
427 | ```
428 |
429 | Now that it can move, let's draw it.
430 |
431 |
432 | ```lua
433 | function love.draw()
434 | for i,row in ipairs(tilemap) do
435 | for j,tile in ipairs(row) do
436 | if tile ~= 0 then
437 | --Draw the image with the correct quad
438 | love.graphics.draw(image, quads[tile], j * width, i * height)
439 | end
440 | end
441 | end
442 |
443 | --Draw the player and multiply its tile position with the tile width and height
444 | love.graphics.draw(player.image, player.tile_x * width, player.tile_y * height)
445 | end
446 | ```
447 |
448 | 
449 |
450 | When you run the game you should be able to walk around with your player. But the problem is that he can walk through walls. Let's fix this by checking if the position he wants to go to is a wall.
451 |
452 |
453 | First make a function called `isEmpty`. Inside we return whether the value on the coordinates equals 0.
454 |
455 | ```lua
456 | function isEmpty(x, y)
457 | return tilemap[y][x] == 0
458 | end
459 | ```
460 |
461 | It might look weird that x and y are turned around, but this is correct. Because the y position is the row and the x position is the column.
462 |
463 | Now that we have our function we can check if where we want to go is an empty spot, and if so it means we can walk.
464 |
465 | ```lua
466 | function love.keypressed(key)
467 | local x = player.tile_x
468 | local y = player.tile_y
469 |
470 | if key == "left" then
471 | x = x - 1
472 | elseif key == "right" then
473 | x = x + 1
474 | elseif key == "up" then
475 | y = y - 1
476 | elseif key == "down" then
477 | y = y + 1
478 | end
479 |
480 | if isEmpty(x, y) then
481 | player.tile_x = x
482 | player.tile_y = y
483 | end
484 | end
485 | ```
486 |
487 | 
488 |
489 | Yay, now our player is trapped inside our walls. Try to see if you can make it pick things up, or have it open a door when you touch a key. Play around with this because that is how you learn.
490 |
491 | ___
492 |
493 | ## Summary
494 |
495 | We can use tiles to make levels. A tilemap is made out of rows and columns. Each row contains a number of columns. Rows are lined up vertically and columns horizontally. We can use a tileset and quads to draw our level.
496 |
--------------------------------------------------------------------------------
/book/chapter11.md:
--------------------------------------------------------------------------------
1 | # Chapter 11 - Classes
2 | Classes are like blueprints. You can create multiple houses with one blueprint. Similarly, we can create multiple objects from one class.
3 |
4 | 
5 |
6 | For classes we're going to use a library: [classic](https://github.com/rxi/classic).
7 |
8 | Click on `classic.lua` and then on Raw, and [copy the code](https://raw.githubusercontent.com/rxi/classic/master/classic.lua).
9 |
10 | Go to your text editor, create a new file called `classic.lua` and paste the code.
11 |
12 | Now we have to require it.
13 |
14 | ```lua
15 | function love.load()
16 | Object = require "classic"
17 | end
18 | ```
19 |
20 | And now we're ready to make a class. Create a new file called `rectangle.lua`, and put in the following code:
21 |
22 | ```lua
23 | --! file: rectangle.lua
24 |
25 | -- Pass Object as first argument.
26 | Rectangle = Object.extend(Object)
27 |
28 | function Rectangle.new(self)
29 | self.test = math.random(1, 1000)
30 | end
31 | ```
32 |
33 | Everything will be explained. But first put this code in your `main.lua`.
34 |
35 |
36 | ```lua
37 | --! file: main.lua
38 | function love.load()
39 | Object = require "classic"
40 | --Don't forget to load the file
41 | require "rectangle"
42 |
43 | r1 = Rectangle()
44 | r2 = Rectangle()
45 | print(r1.test, r2.test)
46 | end
47 | ```
48 |
49 | When you run the game, you´ll see that 2 random numbers are printed.
50 |
51 | So let's go through this code step by step. First we create a new class with `Rectangle = Object.extend(Object)`. This makes `Rectangle` become a class. This will be our blueprint. As opposed to properties, classes are usually written with an uppercase character (so this would be UpperCamelCase or PascalCase).
52 |
53 | In `main.lua` we say `r1 = Rectangle()`. Even though `Rectangle` is a table, we can still call it as if it's a function. How that works is for another chapter. But by calling `Rectangle()` it creates a new instance. What that means is that it takes our blueprint and creates a new object with all the class its features. Every new instance is unique.
54 |
55 | To prove that `r1` is unique, we create another instance called `r2`. Both have the property `test`, but they have different values.
56 |
57 | When we call `Rectangle()`, it executes `Rectangle.new`. This is what we call the *constructor*.
58 |
59 | The parameter `self`, is the instance we're modifying. If we were to type `Rectangle.test = math.random(0, 1000)`, we would be giving the property to the blueprint, and not to an instance made with the blueprint.
60 |
61 | Let's make some changes to our class:
62 |
63 | ```lua
64 | --! file: rectangle.lua
65 | Rectangle = Object.extend(Object)
66 |
67 | function Rectangle.new(self)
68 | self.x = 100
69 | self.y = 100
70 | self.width = 200
71 | self.height = 150
72 | self.speed = 100
73 | end
74 |
75 |
76 | function Rectangle.update(self, dt)
77 | self.x = self.x + self.speed * dt
78 | end
79 |
80 |
81 | function Rectangle.draw(self)
82 | love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
83 | end
84 | ```
85 |
86 | It's like the moving rectangle object we made in [chapter 8](/learn/book/8). Except this time we put the code of the movement and drawing part in the object. Now we only need to call update and draw in `main.lua`.
87 |
88 | ```lua
89 | --! file: main.lua
90 |
91 | function love.load()
92 | Object = require "classic"
93 | require "rectangle"
94 | r1 = Rectangle()
95 | r2 = Rectangle()
96 | end
97 |
98 |
99 | function love.update(dt)
100 | r1.update(r1, dt)
101 | end
102 |
103 |
104 | function love.draw()
105 | r1.draw(r1)
106 | end
107 | ```
108 |
109 | When you run the game you'll see a moving rectangle.
110 |
111 | So we created a class called `Rectangle`. We create an instance of that class called `r1`. So now `r1` has the functions `update` and `draw`. We call these functions, and as first argument we pass the instance itself, `r1`. This is what `self` becomes in the functions.
112 |
113 | It's kind of annoying though, how we have to pass `r1` every time we call one of its functions. Luckily, Lua has a shorthand for this. When we use a colon (:), the function-call will automatically pass the object left of the colon as first argument.
114 |
115 | ```lua
116 | --! file: main.lua
117 | function love.update(dt)
118 | --Lua turns this into: r1.update(r1, dt)
119 | r1:update(dt)
120 | end
121 |
122 |
123 | function love.draw()
124 | --Lua turns this into: r1.draw(r1)
125 | r1:draw()
126 | end
127 | ```
128 |
129 | And we can do this with the functions as well.
130 |
131 |
132 | ```lua
133 | --! file: rectangle.lua
134 |
135 | --Lua turns this into: Object.extend(Object)
136 | Rectangle = Object:extend()
137 |
138 | --Lua turns this into: Rectangle.new(self)
139 | function Rectangle:new()
140 | self.x = 100
141 | self.y = 100
142 | self.width = 200
143 | self.height = 150
144 | self.speed = 100
145 | end
146 |
147 |
148 | --Lua turns this into: Rectangle.update(self, dt)
149 | function Rectangle:update(dt)
150 | self.x = self.x + self.speed * dt
151 | end
152 |
153 |
154 | --Lua turns this into: Rectangle.draw(self)
155 | function Rectangle:draw()
156 | love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
157 | end
158 | ```
159 |
160 | We call this [*Syntactic sugar*](https://en.wikipedia.org/wiki/Syntactic_sugar)
161 |
162 | Let's add some parameters to `Rectangle:new()`
163 |
164 | ```lua
165 | --! file: rectangle.lua
166 | function Rectangle:new(x, y, width, height)
167 | self.x = x
168 | self.y = y
169 | self.width = width
170 | self.height = height
171 | self.speed = 100
172 | end
173 | ```
174 |
175 | With this we can give `r1` and `r2` each their own position and size.
176 |
177 | ```lua
178 | --! file: main.lua
179 |
180 | function love.load()
181 | Object = require "classic"
182 | require "rectangle"
183 | r1 = Rectangle(100, 100, 200, 50)
184 | r2 = Rectangle(350, 80, 25, 140)
185 | end
186 |
187 |
188 | function love.update(dt)
189 | r1:update(dt)
190 | r2:update(dt)
191 | end
192 |
193 |
194 | function love.draw()
195 | r1:draw()
196 | r2:draw()
197 | end
198 | ```
199 | So now we have 2 moving rectangles. This is what makes classes so great. `r1` and `r2` are the same, yet they are unique.
200 |
201 | 
202 |
203 | Another thing that makes classes great is *inheritance*.
204 |
205 | ___
206 |
207 | ## Inheritance
208 |
209 | With inheritance, we can extend our class. In other words, we make a copy of our blueprint, and add features to it, without editing the original blueprint.
210 |
211 | 
212 |
213 | Let's say you have a game with monsters. Every monster has their own attack, they move differently. But they can also get damage, and are able to die. These overlapping features should be put in what we call a *superclass* or *base class*. They provide the features that all monsters have. And then each monster's class can extend this base class and add their own features to it.
214 |
215 | Let's create another moving shape, a circle. What will our moving rectangle and circle have in common? Well they will both move. So let's make a base class for both of our shapes.
216 |
217 | Create a new file called `shape.lua`, and put in the following code:
218 |
219 | ```lua
220 | --! file: shape.lua
221 | Shape = Object:extend()
222 |
223 | function Shape:new(x, y)
224 | self.x = x
225 | self.y = y
226 | self.speed = 100
227 | end
228 |
229 |
230 | function Shape:update(dt)
231 | self.x = self.x + self.speed * dt
232 | end
233 | ```
234 |
235 | Our base class `Shape` now handles the movement. I should point out that *base class* is just a term. "X is a base class of Y". A base class is still the same like any other class. It's just different in how we use it.
236 |
237 | Anyway, now that we have a base class that handles our movement, we can make `Rectangle` an extension of `Shape`, and remove its updater. Make sure to require `shape` before using it.
238 |
239 | ```lua
240 | --! file: main.lua
241 |
242 | function love.load()
243 | Object = require "classic"
244 | require "shape"
245 | require "rectangle"
246 | r1 = Rectangle()
247 | r2 = Rectangle()
248 | end
249 | ```
250 |
251 | ```lua
252 | --! file: rectangle.lua
253 | Rectangle = Shape:extend()
254 |
255 | function Rectangle:new(x, y, width, height)
256 | Rectangle.super.new(self, x, y)
257 | self.width = width
258 | self.height = height
259 | end
260 |
261 | function Rectangle:draw()
262 | love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
263 | end
264 | ```
265 |
266 | With `Rectangle = Shape:extend()` we made `Rectangle` an extension of `Shape`.
267 |
268 | `Shape` has its own function called `:new()`. By creating `Rectangle:new()`, we override the original function. Meaning that when we call `Rectangle()`, it will not execute `Shape:new()` but instead `Rectangle:new()`.
269 |
270 | But rectangle has the property `super`, which is the class `Rectangle` is extended from. With `Rectangle.super` we can get access to our base class's functions, and we use it to call `Shape:new()`.
271 |
272 | We have to pass `self` as first argument ourselves, and can't let Lua handle it with a colon (:), because we're not calling the function as the instance.
273 |
274 | Now we need to make a circle class. Create a new file called `circle.lua`, and put in the following code.
275 |
276 | ```lua
277 | --! file: circle.lua
278 | Circle = Shape:extend()
279 |
280 | function Circle:new(x, y, radius)
281 | Circle.super.new(self, x, y)
282 | --A circle doesn't have a width or height. It has a radius.
283 | self.radius = radius
284 | end
285 |
286 |
287 | function Circle:draw()
288 | love.graphics.circle("line", self.x, self.y, self.radius)
289 | end
290 | ```
291 |
292 | So we make `Circle` an extension of `Shape`. We pass `x` and `y` to the `new()` function of `Shape` with `Circle.super.new(self, x, y)`.
293 |
294 | We give our `Circle` class its own draw function. This is how you draw a circle. Circles don't have a width or height, they have a radius.
295 |
296 | And now in `main.lua` load `shape.lua` and `circle.lua`, and change `r2` to a circle.
297 |
298 | ```lua
299 | --! file: main.lua
300 |
301 | function love.load()
302 | Object = require "classic"
303 | --Don't forget to load the file
304 | require "shape"
305 |
306 | require "rectangle"
307 |
308 | --Don't forget to load the file
309 | require "circle"
310 |
311 | r1 = Rectangle(100, 100, 200, 50)
312 |
313 | --We make r2 a Circle instead of a Rectangle
314 | r2 = Circle(350, 80, 40)
315 | end
316 | ```
317 |
318 | Now when you run the game you'll see a moving rectangle and a moving circle.
319 |
320 | 
321 |
322 | ___
323 |
324 | ## One more time
325 |
326 | Let's go through all this code one more time.
327 |
328 | First we load the library classic with `require "classic"`. Loading this library returns a table, and we store this table inside `Object`. It has the very basics needed to simulate a class. Because Lua doesn't have classes, but by using `classic` we get a very nice imitation of a class.
329 |
330 | Next we load `shape.lua`. In that file we create a new class called `Shape`. We will use this class as a *base class* for `Rectangle` and `Circle`. The 2 things that these classes have in common is that they have an `x` and `y` property, and that it moves horizontally. These similarities is what we put in `Shape`.
331 |
332 | Next we create the `Rectangle` class. We make it an extension of our base class `Shape`. Inside the `:new()` function, the *constructor*, we call the constructor of our base class with `Rectangle.super.new(self, x, y)`. We pass `self` as first argument, so that ` Shape` will use the instance of our blueprint, and not the blueprint itself. We give our rectangle a `width` and `height` property, and give it a draw function.
333 |
334 | Next we repeat the above, except for a circle. So instead of a `width` and `height` we give it a `radius` property.
335 |
336 | Now that we have our classes ready, we can start making *instances* of these classes. With `r1 = Rectangle(100, 100, 200, 50)` we create an instance of our class `Rectangle`. It is an object made out of our blueprint, and not the blueprint itself. Any changes we make to this instance will not affect the class. We update and draw this instance, and for that we use a colon (:). This is because we need to pass our instance as first argument, and the colon will make Lua do it for us.
337 |
338 | And finally we do the same for `r2`, except we make it a `Circle`.
339 |
340 | ___
341 |
342 | ## Confused?
343 |
344 | That was a lot of information for 1 chapter, and I can imagine if you're having a hard time understanding all of this. My advice: Keep following the tutorials. If you're new to programming it will take some time before you get all these new concepts, but eventually you'll get comfortable with them. I will keep adding explanations on older subjects while talking about the newer ones.
345 |
346 | ___
347 |
348 | ## Doing it properly
349 |
350 | In this tutorial I use globals to make things easier. However, a more proper way would be to use locals.
351 |
352 | We do this by changing the class in two ways.
353 |
354 | 1. We make the class a local variable in the file.
355 | 2. We return the variable at the end of the file.
356 |
357 | So here's how we would change shape.lua
358 |
359 | ```lua
360 | --! file: shape.lua
361 | local Shape = Object:extend()
362 |
363 | function Shape:new(x, y)
364 | self.x = x
365 | self.y = y
366 | self.speed = 100
367 | end
368 |
369 |
370 | function Shape:update(dt)
371 | self.x = self.x + self.speed * dt
372 | end
373 |
374 | return Shape
375 | ```
376 |
377 | Then inside the other files of the classes, we require the shape file, and pass its value onto a variable. Just like how we do with classic.
378 |
379 | ```lua
380 | --! file: rectangle.lua
381 | --We could name our variable anything we want here, but it's nice to keep a consistent name.
382 | local Shape = require "shape"
383 |
384 | --Let's also make rectangle local.
385 | local Rectangle = Shape:extend()
386 |
387 | function Rectangle:new(x, y, width, height)
388 | Rectangle.super.new(self, x, y)
389 | self.width = width
390 | self.height = height
391 | end
392 |
393 | function Rectangle:draw()
394 | love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
395 | end
396 |
397 | -- And then return it.
398 | return Rectangle
399 | ```
400 |
401 | Finally, in main.lua, we can also create a local variable.
402 |
403 | ```lua
404 | --! file: main.lua
405 |
406 | function love.load()
407 | Object = require "classic"
408 | --We no longer need to require shape here.
409 |
410 | local Rectangle = require "rectangle"
411 |
412 | local Circle = require "circle"
413 |
414 | r1 = Rectangle(100, 100, 200, 50)
415 |
416 | r2 = Circle(350, 80, 40)
417 | end
418 | ```
419 |
420 | We could also move `Object = require "classic"` to `shape.lua`, and make it a local as well. `r1` and `r2` could also be made local variables. but they will need to be made outside of the `love.load`.
421 |
422 | ```lua
423 | --! file: main.lua
424 |
425 | --By making these variables r1 and r2 are no longer global.
426 | local r1, r2
427 |
428 | function love.load()
429 | Object = require "classic"
430 |
431 | local Rectangle = require "rectangle"
432 |
433 | local Circle = require "circle"
434 |
435 | r1 = Rectangle(100, 100, 200, 50)
436 |
437 | r2 = Circle(350, 80, 40)
438 | end
439 | ```
440 |
441 | This might seem like a lot of extra work for the same result, but your code will be much cleaner. Imagine creating a game with a global `score` variable, but in your game is also a minigame with its own score system. By accident you could override the global `score` variable, and you end up confused why your score is not working properly. Globals can still be used, but use them sparingly.
442 |
443 | ___
444 |
445 | ## Summary
446 |
447 | Classes are like blueprints. We can create multiple objects out of 1 class. To simulate classes we use the library classic. You create a class with `ClassName = Object:extend()`. You create an instance of a class with `instanceName = ClassName()`. This will call the function `ClassName:new()`. This is called the *constructor*. Every function of a class should start with the parameter `self` so that when calling the function you can pass the instance as first argument. `instanceName.functionName(instanceName)`. We can use colons (:) to make Lua do this for us.
448 |
449 | We can extend a class with `ExtensionName = ClassName:extend()`. This makes `ExtensionName` a copy of `ClassName` that we can add properties to without editing `ClassName`. If we give `ExtensionName` a function that `ClassName` already has, we can still call the original function with `ExtensionName.super.functionName(self)`.
450 |
451 | By using local variables, our code will be much cleaner and easier to maintain.
--------------------------------------------------------------------------------
/book/chapter21.md:
--------------------------------------------------------------------------------
1 | # Chapter 21 - Saving and loading
2 |
3 | Let's make it so that we can save and load our progress in our game. We do this by writing and reading a file. But first we need to have a game, so let's make a small game where you can pick up coins.
4 |
5 | We start with a player that can move.
6 |
7 | ```lua
8 | function love.load()
9 | -- Create a player object with an x, y and size
10 | player = {
11 | x = 100,
12 | y = 100,
13 | size = 25
14 | }
15 | end
16 |
17 | function love.update(dt)
18 | -- Make it moveable with keyboard
19 | if love.keyboard.isDown("left") then
20 | player.x = player.x - 200 * dt
21 | elseif love.keyboard.isDown("right") then
22 | player.x = player.x + 200 * dt
23 | end
24 |
25 | -- Note how I start a new if-statement instead of contuing the elseif
26 | -- I do this because else you wouldn't be able to move diagonally.
27 | if love.keyboard.isDown("up") then
28 | player.y = player.y - 200 * dt
29 | elseif love.keyboard.isDown("down") then
30 | player.y = player.y + 200 * dt
31 | end
32 | end
33 |
34 | function love.draw()
35 | -- The players and coins are going to be circles
36 | love.graphics.circle("line", player.x, player.y, player.size)
37 | end
38 | ```
39 |
40 | And for fun, let's give the player a face.
41 |
42 | 
43 |
44 | ```lua
45 | function love.load()
46 | -- Create a player object with an x, y and size
47 | player = {
48 | x = 100,
49 | y = 100,
50 | size = 25,
51 | image = love.graphics.newImage("face.png")
52 | }
53 | end
54 |
55 | function love.draw()
56 | love.graphics.circle("line", player.x, player.y, player.size)
57 | -- Set the origin of the face to the center of the image
58 | love.graphics.draw(player.image, player.x, player.y,
59 | 0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
60 | end
61 | ```
62 |
63 |
64 | Next we want to add some coins. We'll have them positioned randomly on screen. And let's give them a small dollar sign.
65 |
66 | 
67 |
68 | ```lua
69 | function love.load()
70 | player = {
71 | x = 100,
72 | y = 100,
73 | size = 25,
74 | image = love.graphics.newImage("face.png")
75 | }
76 |
77 | coins = {}
78 |
79 | for i=1,25 do
80 | table.insert(coins,
81 | {
82 | -- Give it a random position with math.random
83 | x = math.random(50, 650),
84 | y = math.random(50, 450),
85 | size = 10,
86 | image = love.graphics.newImage("dollar.png")
87 | }
88 | )
89 | end
90 | end
91 |
92 | function love.update(dt)
93 | if love.keyboard.isDown("left") then
94 | player.x = player.x - 200 * dt
95 | elseif love.keyboard.isDown("right") then
96 | player.x = player.x + 200 * dt
97 | end
98 |
99 | if love.keyboard.isDown("up") then
100 | player.y = player.y - 200 * dt
101 | elseif love.keyboard.isDown("down") then
102 | player.y = player.y + 200 * dt
103 | end
104 | end
105 |
106 | function love.draw()
107 | love.graphics.circle("line", player.x, player.y, player.size)
108 | love.graphics.draw(player.image, player.x, player.y,
109 | 0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
110 |
111 | for i,v in ipairs(coins) do
112 | love.graphics.circle("line", v.x, v.y, v.size)
113 | love.graphics.draw(v.image, v.x, v.y,
114 | 0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
115 | end
116 | end
117 | ```
118 |
119 | Now we want to be able to pick them up. For that we need to check if there is collision. We do this by calculating the distance, and then checking if the distance is smaller than the radius of both circles summed up.
120 |
121 | 
122 |
123 |
124 | ```lua
125 | function checkCollision(p1, p2)
126 | -- Calculating distance in 1 line
127 | -- Subtract the x's and y's, square the difference
128 | -- Sum the squares and find the root of the sum.
129 | local distance = math.sqrt((p1.x - p2.x)^2 + (p1.y - p2.y)^2)
130 | -- Return whether the distance is lower than the sum of the sizes.
131 | return distance < p1.size + p2.size
132 | end
133 | ```
134 |
135 | And now we iterate through all the coins and check if it's touching the player. Let's make it so that the player grows when picking up a coin.
136 |
137 | ```lua
138 | function love.update(dt)
139 | if love.keyboard.isDown("left") then
140 | player.x = player.x - 200 * dt
141 | elseif love.keyboard.isDown("right") then
142 | player.x = player.x + 200 * dt
143 | end
144 |
145 | if love.keyboard.isDown("up") then
146 | player.y = player.y - 200 * dt
147 | elseif love.keyboard.isDown("down") then
148 | player.y = player.y + 200 * dt
149 | end
150 |
151 | for i,v in ipairs(coins) do
152 | if checkCollision(player, v) then
153 | table.remove(coins, i)
154 | player.size = player.size + 1
155 | end
156 | end
157 | end
158 | ```
159 |
160 | 
161 |
162 | Now we can move around and pick up coins. Nice! But before we start saving and loading, let's make a few more changes.
163 |
164 | If you restart the game a few times you might notice that even though the circles are positioned randomly, they are always on the same random spot.
165 |
166 | To fix this we can use `math.randomseed()`. The random numbers you generate are based on a number, which we call the *seed*. And because we don't change the seed you always get the same random positions. The advantage of seeds is that they are a key to a certain randomness, which you could save and share. With Minecraft for example, you can share the seed that was used to generate a world, and other people can use it to get the same generated world.
167 |
168 | So what number do we use as our seed? Because if we were to do `math.randomseed(123)`, the game would still have the same number. We need a number that is unique every time we start the game. And for that we can use `os.time()`. This is a Lua function that gives you the time of your operation system to the second. That's 86400 unique numbers a day!
169 |
170 | But even better might be to use LÖVE's math library. LÖVE's random number generated (rng) is automatically seeded (with os.time()) and overall is better/more random than Lua's rng.
171 |
172 | ```lua
173 | function love.load()
174 | player = {
175 | x = 100,
176 | y = 100,
177 | size = 25,
178 | image = love.graphics.newImage("face.png")
179 | }
180 |
181 | coins = {}
182 |
183 | for i=1,25 do
184 | table.insert(coins,
185 | {
186 | x = love.math.random(50, 650),
187 | y = love.math.random(50, 450),
188 | size = 10,
189 | image = love.graphics.newImage("dollar.png")
190 | }
191 | )
192 | end
193 | end
194 | ```
195 |
196 | Another thing that bugs me is the for-loop in which we remove coins. Now, picking up coins works fine, but in general when you are iterating through a list, and you're removing items from that list, you want to use a more safe approach. Because by removing an item, the list gets shorter and it messes up the for-loop.
197 |
198 | Try out this code for example:
199 |
200 | ```lua
201 | local test = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
202 | for i,v in ipairs(test) do
203 | table.remove(test, i)
204 | end
205 | print(#test)
206 | -- Output: 5
207 | ```
208 | It outputs 5, meaning it only emptied half of the list. This happens because as it removes an item from the list, everything gets moved to the side, and items get skipped over.
209 |
210 | 
211 |
212 | To fix this we should loop through the table in reverse. This way you will never skip over any items when you have remove something.
213 |
214 | 
215 |
216 | This means that we won't be using `ipairs` but instead a normal for-loop, but backwards.
217 |
218 | ```lua
219 | function love.update(dt)
220 | if love.keyboard.isDown("left") then
221 | player.x = player.x - 200 * dt
222 | elseif love.keyboard.isDown("right") then
223 | player.x = player.x + 200 * dt
224 | end
225 |
226 | if love.keyboard.isDown("up") then
227 | player.y = player.y - 200 * dt
228 | elseif love.keyboard.isDown("down") then
229 | player.y = player.y + 200 * dt
230 | end
231 |
232 | -- Start at the end, until 1, with steps of -1
233 | for i=#coins,1,-1 do
234 | -- Use coins[i] instead of v
235 | if checkCollision(player, coins[i]) then
236 | table.remove(coins, i)
237 | player.size = player.size + 1
238 | end
239 | end
240 | end
241 | ```
242 |
243 | Alright, time to start saving the game.
244 |
245 | ___
246 |
247 | ## Saving
248 |
249 | So what we do for saving the game is that we make a table, and in this table we put all the information that we want to save. So what do we want to save? How about the player's position, his size and the same for the coins that haven't been picked up yet. So let's create the function `saveGame()` where we store the important data in a table.
250 |
251 | ```lua
252 | function saveGame()
253 | data = {}
254 | data.player = {
255 | x = player.x,
256 | y = player.y,
257 | size = player.size
258 | }
259 |
260 | data.coins = {}
261 | for i,v in ipairs(coins) do
262 | -- In this case data.coins[i] = value is the same as table.insert(data.coins, value )
263 | data.coins[i] = {x = v.x, y = v.y}
264 | end
265 | end
266 | ```
267 |
268 | So why save these specific parts and not just the whole table? Well in general you don't want to use more data then you need. We don't need to save the image width and height because it will always be the same. Also, our player object has an image object, and we can't save LÖVE objects.
269 |
270 | So now that we have all the information we need to *serialize* it. This means that we need to make the table into something that we can read. Because right now when you print table you might get something like `table: 0x00e4ca20`, and that's not the information we want to save.
271 |
272 | To serialize the table we're going to use *lume* which is a utility library by *rxi*. You can find the library on [GitHub](https://github.com/rxi/lume).
273 |
274 | Click on `lume.lua` and then on Raw, and [copy the code](https://raw.githubusercontent.com/rxi/lume/master/lume.lua).
275 |
276 | Go to your text editor, create a new file called `lume.lua` and paste the code.
277 |
278 | Load it with `lume = require "lume"` at the top of `love.load()` in `main.lua`.
279 |
280 | Lume has all kinds of neat functions, but the important ones for this is tutorial are `serialize` and `deserialize`. Let's try it out. Serialize the data table and then print its value.
281 |
282 | ```lua
283 | function saveGame()
284 | data = {}
285 | data.player = {
286 | x = player.x,
287 | y = player.y,
288 | size = player.size
289 | }
290 |
291 | data.coins = {}
292 | for i,v in ipairs(coins) do
293 | -- In this case data.coins[i] = value is the same as table.insert(data.coins, value )
294 | data.coins[i] = {x = v.x, y = v.y}
295 | end
296 |
297 | serialized = lume.serialize(data)
298 | print(serialized)
299 | end
300 | ```
301 |
302 | In your output you'll see the table printed in a way that you can read it. This is what we'll be saving to our file, which is the next step.
303 |
304 | We can create, edit and read files with the `love.filesystem` ([wiki](https://www.love2d.org/wiki/love.filesystem)) module. For create/writing a file we use `love.filesystem.write(filename, data)` ([wiki](https://www.love2d.org/wiki/love.filesystem.write)). The first argument is the name of the file, the second argument is the data that we want to write to the file.
305 |
306 | ```lua
307 | function saveGame()
308 | data = {}
309 | data.player = {
310 | x = player.x,
311 | y = player.y,
312 | size = player.size
313 | }
314 |
315 | data.coins = {}
316 | for i,v in ipairs(coins) do
317 | -- In this case data.coins[i] = value is the same as table.insert(data.coins, value )
318 | data.coins[i] = {x = v.x, y = v.y}
319 | end
320 |
321 | serialized = lume.serialize(data)
322 | -- The filetype actually doesn't matter, and can even be omitted.
323 | love.filesystem.write("savedata.txt", serialized)
324 | end
325 | ```
326 |
327 | Now we need to make that you can save. Let's make it so that you save when you press *f1*.
328 |
329 | ```lua
330 | function love.keypressed(key)
331 | if key == "f1" then
332 | saveGame()
333 | end
334 | end
335 | ```
336 |
337 | Run the game, grab some coins and press F1. Just like that we made our first save file! So where is it? If you're on Windows it's saved in `AppData\Roaming\LOVE\`. You can get to the hidden AppData folder by pressing Win + R, followed by typing *"appdata"* and click on OK. Then go to Roaming and then to LOVE. There should be a folder that has the same name as the folder your LÖVE project is in. And in that folder you will find a file named `savedata.txt`. If you open the file you'll see that your table is inside.
338 |
339 | Now let's make it so that we can load our data.
340 |
341 | ___
342 |
343 | ## Loading
344 |
345 | To load our data we need to:
346 |
347 | * Check if a save file exists
348 | * Read the file
349 | * Turn the data into a table
350 | * Apply the data to our players and coins.
351 |
352 | So let's start with checking if our file exists, and if so we read the file. We can do this with `love.filesystem.getInfo(filename)` and `love.filesystem.read(filename)`.
353 | If a file exists, `love.filesystem.getInfo(filename)` will return a table with information, else it will return `nil`. Since we only want to know if the file exists we can put the function in an if-statement, because we don't need to the information that the table provides.
354 |
355 | ```lua
356 | function love.load()
357 |
358 | lume = require "lume"
359 |
360 | player = {
361 | x = 100,
362 | y = 100,
363 | size = 25,
364 | image = love.graphics.newImage("face.png")
365 | }
366 |
367 | coins = {}
368 |
369 | if love.filesystem.getInfo("savedata.txt") then
370 | file = love.filesystem.read("savedata.txt")
371 | print(file)
372 | end
373 |
374 | for i=1,25 do
375 | table.insert(coins,
376 | {
377 | x = love.math.random(50, 650),
378 | y = love.math.random(50, 450),
379 | size = 10,
380 | image = love.graphics.newImage("dollar.png")
381 | }
382 | )
383 | end
384 | end
385 | ```
386 |
387 | Run the game and it should print our save data. Now we need to turn this table string into a real table. We can do this with `lume.deserialize`.
388 |
389 |
390 | ```lua
391 | if love.filesystem.getInfo("savedata.txt") then
392 | file = love.filesystem.read("savedata.txt")
393 | data = lume.deserialize(file)
394 | end
395 | ```
396 |
397 | And now we can apply the data to our player and coins. Now how we put this code before filling in the coins table. That's because we don't want generate the coins that we have already picked up in our save file. Which coins we add is now based on the data.
398 |
399 |
400 | ```lua
401 | function love.load()
402 |
403 | lume = require "lume"
404 |
405 | player = {
406 | x = 100,
407 | y = 100,
408 | size = 25,
409 | image = love.graphics.newImage("face.png")
410 | }
411 |
412 | coins = {}
413 |
414 | if love.filesystem.getInfo("savedata.txt") then
415 | file = love.filesystem.read("savedata.txt")
416 | data = lume.deserialize(file)
417 |
418 | --Apply the player info
419 | player.x = data.player.x
420 | player.y = data.player.y
421 | player.size = data.player.size
422 |
423 | for i,v in ipairs(data.coins) do
424 | coins[i] = {
425 | x = v.x,
426 | y = v.y,
427 | size = 10,
428 | image = love.graphics.newImage("dollar.png")
429 | }
430 | end
431 | else
432 | -- Only execute this if you don't have a save file
433 | for i=1,25 do
434 | table.insert(coins,
435 | {
436 | x = love.math.random(50, 650),
437 | y = love.math.random(50, 450),
438 | size = 10,
439 | image = love.graphics.newImage("dollar.png")
440 | }
441 | )
442 | end
443 | end
444 | end
445 | ```
446 |
447 | Now when you run the game you'll see that it loads your save file. You can grab some more coins, press F1 to save, and when you restart the game you'll see that it again has saved and loaded your game. Awesome! But what if we want to restart? Let's add a button that deletes our save file so that we can start a new game.
448 |
449 | ___
450 |
451 | ## Resetting
452 |
453 | To remove our save file we can use `love.filesystem.remove(filename)`. Let's make it so that when we press *F2* the file is removed and it restarts the game. We can quit the game with `love.event.quit()`, but if we pass `"restart"` as first argument, the game will restart instead.
454 |
455 | ```lua
456 | function love.keypressed(key)
457 | if key == "f1" then
458 | saveGame()
459 | elseif key == "f2" then
460 | love.filesystem.remove("savedata.txt")
461 | love.event.quit("restart")
462 | end
463 | end
464 | ```
465 |
466 | And there we go, we can now reset our game!
467 |
468 | ___
469 |
470 | ## Summary
471 |
472 | *Seeds* decide which random values you generate, and this can be used to share a randomly generated level for example. We can also use LÖVE's math module. When removing items from a list we should loop through the table in reverse to prevent items from being skipped. We can create a save file by adding important data to a table, then turn that table into a string and write that string to a file with `love.filesystem.write(filename)`. We can load a save file by reading the file with `love.filesystem.read(filename)`, deserializing the data and applying the data to our objects. We can remove a file with `love.filesystem.remove(filename)` and restart the game with `love.event.quit("restart")`.
473 |
--------------------------------------------------------------------------------