├── 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 |
3 | 80 |
81 |

Bonus chapters

82 |
83 | 88 |
-------------------------------------------------------------------------------- /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 | ![](/images/book/15/compress.png) 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 | ![](/images/book/15/control_panel.png) 41 | 42 | Click on ***File Explorer options***. 43 | 44 | ![](/images/book/15/personalization.png) 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 | ![](/images/book/15/folder_options.png) 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 | ![](/images/book/14/demo.gif) 42 | 43 | ![](/images/book/16/following_circle_distance.gif) 44 | 45 | ![](/images/book/17/jump_help.png) ![](/images/book/17/jump.gif) 46 | 47 | ![](/images/book/18/tile-move-2.gif) 48 | 49 | ![](/images/book/22/splitscreen.gif) 50 | 51 | ![](/images/book/23/box_wall_good.gif) 52 | 53 | ![](/images/book/24/jumping.gif) 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 | ![](/images/book/5/rectangle.png) 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 | ![](/images/book/5/coordinates.png) 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 | ![](/images/book/5/rectangle_move.gif) 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 | ![](/images/book/8/moving_rectangles.gif) 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 | ![](/images/book/1/download_love.png) 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 | ![](/images/book/1/download_brane.png) 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 | ![](/images/book/1/install_brane.png) 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 | ![](/images/book/1/project_brane.png) 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 | [![](/images/book/4/rectangle.png "love2d.org/wiki/love.graphics.rectangle")](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 | [![](/images/book/4/drawmode.png "love2d.org/wiki/DrawMode")](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 | ![](/images/book/4/example_rectangle.png) 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 | ![](/images/book/12/sheep.png) 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 | ![](/images/book/12/origin1.png) 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 | ![](/images/book/12/origin2.png) 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 | ![](/images/book/12/shear.png) 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 | ![](/images/book/12/color.png) 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 | ![Guide](/images/book/bonus/vscode/lovepath.gif) 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. ![debugging](/images/book/bonus/vscode/debugging.png) 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 | ![error](/images/book/bonus/vscode/error.png) 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 | ![](/images/book/6/rectangle_stop.gif) 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 | ![](/images/book/6/rectangle_right.gif) 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 | ![](/images/book/13/rectangles1.png) 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 | ![](/images/book/13/rectangles2.png) 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 | ![](/images/book/13/rectangles3.png) 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 | ![](/images/book/9/scope.png) 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 | ![](/images/book/7/table.png) 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 | ![](/images/book/7/fruits.png) 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 | ![](/images/book/7/shift.png) 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 | ![](/images/book/20/error1.png) 110 | 111 | Another example is trying to call a function that doesn't exist. 112 | 113 | ```lua 114 | doesNotExist() 115 | ``` 116 | 117 | ![](/images/book/20/error2.png) 118 | 119 | In our bullet shooting game we get the following error: 120 | 121 | ![](/images/book/20/error3.png) 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 | ![](/images/book/20/error4.png) 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 | ![](/images/book/20/error5.png) 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 | ![](/images/book/20/error6.png) 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 | ![](/images/book/20/error7.png) 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 | ![](/images/book/20/hendrik.png) 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 | ![](/images/book/16/atan2.png) 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 | ![](/images/book/16/angle.gif) 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 | ![](/images/book/16/radian.gif) 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 | ![](/images/book/16/pi.gif) 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 | ![](/images/book/16/sinecosine.gif) 100 | 101 | And here's an image to show what exactly is going on in the gif. 102 | 103 | ![](/images/book/16/sinecosine2.png) 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 | ![](/images/book/16/sinecosine3.png) 110 | 111 | If the angle would point down then cosine would be 0 and sine would be 1. 112 | 113 | ![](/images/book/16/sinecosine4.png) 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 | ![](/images/book/16/following_circle.gif) 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 | ![](/images/book/16/pythagorean.png) 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 | ![](/images/book/16/triangle.png) 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 | ![](/images/book/16/following_circle_distance.gif) 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 | ![](/images/book/16/following_circle_distance_speed.gif) 268 | 269 | ___ 270 | 271 | ## Image 272 | Let's use an image and make it look at the cursor. 273 | 274 | ![](/images/book/16/arrow_right.png) 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 | ![](/images/book/16/arrow_off.png) 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 | ![](/images/book/16/following_arrow.gif) 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 | ![](/images/book/17/jump1.png), ![](/images/book/17/jump2.png), ![](/images/book/17/jump3.png), ![](/images/book/17/jump4.png) and ![](/images/book/17/jump5.png) 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 | ![](/images/book/17/jump.gif) 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 | ![](/images/book/17/jump.png) 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 | ![](/images/book/17/quad_position.png) 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 | ![](/images/book/17/jump_help.png) 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 | ![](/images/book/17/jump_2.png) 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 | ![](/images/book/17/rectangles1.png) 297 | 298 | Our first frame could end up like this: 299 | 300 | ![](/images/book/17/bleeding.png) 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 | ![](/images/book/17/bleeding_fix.png) 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 | ![](/images/book/17/jump_3.png) 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 | ![](/images/book/17/almost.png) 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 | ![](/images/book/17/whatisgoingon.png) 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 | ![](/images/book/14/demo.gif) 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 | ![](/images/book/14/panda.png) 14 | ![](/images/book/14/snake.png) 15 | ![](/images/book/14/bullet.png) 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 | ![](/images/book/18/one_row.png) 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 | ![](/images/book/18/table.png) 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 | ![](/images/book/18/houses.png) 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 | ![](/images/book/18/2d_table.gif) 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 | ![](/images/book/18/colors.png) 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 | ![](/images/book/18/tile.png) 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 | ![](/images/book/18/colors_image.png) 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 | ![](/images/book/18/tileset.png) 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 | ![](/images/book/18/numbered.png) 312 | 313 | So if we wanted to create this: 314 | 315 | ![](/images/book/18/level.png) 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 | ![](/images/book/18/player.png) 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 | ![](/images/book/18/tile-move-1.gif) 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 | ![](/images/book/18/tile-move-2.gif) 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 | ![](/images/book/11/blueprint.png) 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 | ![](/images/book/11/moving_rectangles_classes.gif) 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 | ![](/images/book/11/extension.png) 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 | ![](/images/book/11/moving_rectangle_circle.gif) 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 | ![](/images/book/21/face.png) 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 | ![](/images/book/21/dollar.png) 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 | ![](/images/book/21/circles_collision.png) 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 | ![](/images/book/21/coin_grower.gif) 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 | ![](/images/book/21/table_remove.png) 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 | ![](/images/book/21/table_remove_reverse.png) 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 | --------------------------------------------------------------------------------