├── 1SuperPong ├── README.md ├── ch1.md ├── ch2.md ├── ch3.md ├── ch4.md ├── ch5.md └── img │ ├── actors.png │ ├── arcadebody.png │ ├── behavior.png │ ├── camera.png │ ├── menu.png │ ├── menustructure.png │ ├── pong.png │ ├── settingassets.png │ └── spriteset.png ├── 2SuperOXO ├── README.md ├── ch1.md ├── ch2.md ├── ch3.md ├── ch4.md ├── ch5.md ├── ch6.md └── img │ ├── 3D.png │ ├── assets.png │ ├── behavior.png │ ├── board.png │ ├── camera.png │ ├── screens.png │ ├── symbols.png │ └── tictactoe.png ├── 3SuperSokoban ├── README.md ├── ch1.md ├── ch2.md ├── ch3.md ├── ch4.md ├── ch5.md ├── ch6.md └── img │ ├── assets.png │ ├── character.png │ ├── check.png │ ├── level1.png │ ├── level1actors.png │ ├── level1world.png │ ├── level2.png │ ├── level3.png │ ├── next.png │ ├── scene.png │ ├── settings.png │ ├── sokoban.png │ └── template.png ├── LICENSE.txt └── README.md /1SuperPong/README.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #1 SUPER PONG 2 | ## *Start to learn game development with a PONG game* 3 | 4 | ### Summary 5 | 6 | 1. [Introduction and Plan the game](ch1.md#chapter-1--introduction-and-plan-the-game) 7 | * [Introduction to game development](ch1.md#chapter-1--introduction-and-plan-the-game) 8 | * [Plan of our game](ch1.md#plan-the-game) 9 | 2. [Preparing Superpowers](ch2.md#chapter-2--preparing-superpowers) 10 | * [Building the project structure](ch2.md#building-the-project-structure) 11 | * [Loading Game Assets](ch2.md#loading-the-game-assets) 12 | * [Building Game Scene](ch2.md#building-the-scene) 13 | * [Setting Arcade Bodies](ch2.md#setting-arcade-bodies) 14 | 3. [Programming the game logic](ch3.md#chapter-3--programming-the-game-logic) 15 | * [Introduction to programming](ch3.md#introduction) 16 | * [Behavior of the paddles](ch3.md#scripting-the-paddles-behaviors) 17 | * [Behavior of the ball](ch3.md#scripting-the-ball-behavior) 18 | * [Display score points](ch3.md#score-system) 19 | 4. [Polishing the game](ch4.md#chapter-4--polishing-the-game) 20 | * [Load Menu Assets](ch4.md#build-the-menu-structure-and-load-assets-files) 21 | * [Build Menu Scene](ch4.md#build-the-menu-scene) 22 | * [Scripting a Button](ch4.md#scripting-a-button) 23 | * [Add sound effect](ch4.md#adding-sound) 24 | * [Finishing the game](ch4.md#adding-an-end-to-the-game) 25 | 5. [Complete Game Source Reference](ch5.md#chapter-5--complete-game-source-reference) 26 | * [Learning game development](ch5.md#final-considerations-about-the-learning-process-of-making-video-games) 27 | * [Complete Game Source Reference](ch5.md#complete-game-source-reference) 28 | 29 | ### What we will learn in this tutorial 30 | 31 | - To discover how easy it is to become a game developer (it is just about doing it) 32 | - How to setup a first game project 33 | - How to create a simple structure for a game and load assets 34 | - How to build a scene with Actors and add components (camera, sprite renderer, text renderer, behavior) 35 | - Code a game with the superpowers API and TypeScript 36 | - Sup.Actor 37 | - Sup.Input 38 | - Sup.ArcadePhysics2D.Body (Sup.ArcadeBody2D) 39 | - Sup.getActor( string ).getChild( string ) 40 | - Sup.Math.Ray 41 | - Audio.playsound 42 | - Sup.loadScene 43 | - To discover than now, we are a game developer and than we can learn anything 44 | -------------------------------------------------------------------------------- /1SuperPong/ch1.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #1 SUPER PONG 2 | ## *Chapter 1 : Introduction and Plan the game* 3 | 4 | ### Introduction to the new comer who dream to become a game developer 5 | 6 | We are going to build a Pong game, it is a simple game that will be an opportunity for us to discover Superpowers as an empowering tool to build video games. It is a first step that can seem simple, but understanding this first step of what is game development is really important before going on the next step and building our next game, and even to continue after that to do many games in an iterative process until we build the wonderful game we have in the mind and want to bring to life. 7 | 8 | Don't quit with the first difficulties or mistakes (which in fact are really important for the learning process) and don't let yourself build the belief than you are not 'made' for this and others are better than you, that's not true, everyone can do anything with patience and a playful mind. Always start from where you are and not from where you should be. 9 | 10 | Making game can be really difficult and game developers succeed not because they are different than you but mainly because they engage a LOT of time in it, they are building their skills step by step, do a lot (a LOT) of mistakes and they don't have the belief they won't be able to learn something new from them, oh and also they enjoy a lot the simple process of building games because just focusing on the end result can be really frustrating and tiresome. Focus on now, the future take care of itself. 11 | 12 | I know that game development can be impressive from the point of view of the newcomer who looks behind the scene of games they like to play. But believe me, this impressiveness is an illusion built by a huge amount of time and dedication. Don't lose time thinking you aren't able to do the same, step up to become game developer right now if it is what your dream is about. 13 | 14 | Ok, let's make a first game. 15 | 16 | ### Plan the game 17 | 18 | We will make [Pong][1], two players trying to catch a ball and earn points if the other player fail to catch it back. 19 | 20 | ![pong.png](img/pong.png) 21 | 22 | Pong is a kind of symbol in the young video game history, as it is one of the first graphical video game. I think it is also a nice starter for learning the basics of Superpowers. 23 | 24 | You can [play the game here][2] if you want to see what we will do. 25 | 26 | Our game will have the following features than we will build step by step: 27 | 28 | * A table background, in which the game takes place. 29 | * Two paddles that each player can control with the keyboard. 30 | * One ball that moves and bounces off the table sides and off the paddles. 31 | * A score displaying the points. 32 | * A menu with a start button and game instructions. 33 | 34 | We will build the game without computer artificial intelligence, meaning than we will need two players to play the game, we can also have great fun doing a competition between our left and right hand. My left hand always win, she has more training playing with the keyboard I guess... :) 35 | 36 | *(I will do others tutorials later that add AI to this game and others)* 37 | 38 | Next: [Preparing Superpowers](ch2.md#chapter-2--preparing-superpowers) 39 | 40 | [1]: https://en.wikipedia.org/wiki/Pong 41 | [2]: http://mseyne.itch.io/pong 42 | -------------------------------------------------------------------------------- /1SuperPong/ch2.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #1 SUPER PONG 2 | ## *Chapter 2 : Preparing Superpowers* 3 | 4 | ### Building the project structure 5 | 6 | *I'm assuming you already have Superpowers installed and working so we can jump directly to the game. 7 | If you want to learn the basics of the application please check the [official documentation][1].* 8 | 9 | We'll start our server and create our **new project** that we'll call PongTutorial. 10 | 11 | We'll start by creating a **Scene** (new asset or ctrl+n) called **Game**. 12 | 13 | In **Settings**, put Game in the **Startup Scene** (drag and drop or write Game). A gray `Startup Scene` label should appear on the sidebar next to Game. We'll also set the **Screen ratio** to (400, 300) 14 | 15 | We'll create a hierarchy structure for our assets, putting sprites files inside 16 | GameSprites, sounds in GameSounds and scripts in GameScripts. 17 | 18 | We'll create new assets, 4 Sprites, 3 Sounds and 2 Scripts, and also add a Font asset. You can leave the assets empty for now, we'll add their content below. 19 | 20 | *Note: The order of your assets and folders does not matter.* 21 | 22 | * Game 23 | * GameSprites 24 | * background 25 | * pad1 26 | * pad2 27 | * ball 28 | * GameScripts 29 | * Ball 30 | * Paddles 31 | * GameSounds 32 | * tac 33 | * toc 34 | * tada 35 | * Font 36 | 37 | ![settingassets.png](img/settingassets.png) 38 | 39 | 40 | ### Loading the game assets 41 | 42 | *An asset is a box holding an element of the game, it can be an image, a script, a sound, etc.* 43 | 44 | We have built the game assets structure but they will stay empty if we don't give 45 | them contents (which is files for sprites and sounds and code for scripts). 46 | 47 | First you will need to download the source assets on [the repository github here][2]. 48 | 49 | The sprites and sound assets need to be uploaded inside the project. 50 | For each asset, we load the related file (For example, **background.png** in asset **background**). 51 | 52 | After uploading the images, the sprites assets need calibration by clicking **Setup**. The **grid size** must be the same size than 53 | the image because we don't use frames for this game. 54 | Setup will ask you "How many frames per row?" - we'll accept the default frames/row and frames/column which is 1 for both. 55 | 56 | Change the rendering **Pixels / Unit** of each Sprite assets to 50 (default is 100). 57 | 58 | We don't need to change the **origin** of our sprites. The default percentage (50, 50) sets the origin to the center center which is good for all of our sprites for this game. 59 | 60 | Here the example of pad2 with the correct set up. 61 | 62 | ![spriteset.png](img/spriteset.png) 63 | 64 | In the Font Asset, we'll change the **Pixels / Unit** to 100 and the **Size** to 64. 65 | 66 | After loading the sound file into the Sound assets, everything should be fine, you can just test each sound. 67 | 68 | We are now finished with the assets, and can start to build our game scene. 69 | 70 | 71 | ### Building the scene 72 | 73 | *The Scene is the screen where all objects (called actors) are connected together.* 74 | 75 | We will now set all the objects of the scene. We call an object inside the scene an Actor. 76 | 77 | To see the others Actors inside the game, we first need a Camera Actor. 78 | 79 | *Note : We can switch the camera mode 2D / 3D to navigate inside the scene, see documentation.* 80 | 81 | We'll create a **new actor** that we'll name **Camera**. 82 | 83 | Set the **position** on axis Z (the third) to 5 to place the camera to the front of all 84 | others objects who have a position on axis Z less than 5 (Position will be 0, 0, 5). 85 | 86 | Add a **new component** of type Camera. 87 | 88 | Change the mode to **Orthographic**. 89 | 90 | Now set the **Orthographic Scale** to 6 (we do this to fit with the background). 91 | 92 | ![camera.png](img/camera.png) 93 | 94 | Now we'll create all of the Actor objects, following this organization of actors : 95 | 96 | ![actors.png](img/actors.png) 97 | 98 | We'll add a **new component** of type **Sprite Renderer** in Background Actor and select the Sprite 99 | background as the Sprite. (or write the path GameSprites/background) 100 | 101 | We'll do this step again for the Actors Ball, and the two paddles of Player1 and Player2. 102 | For each of them we'll create a new component **Sprite Renderer** and connect the Sprite 103 | Path of the correct Sprite. Pad1 for the Paddle of Player1, etc. 104 | 105 | We'll create a new component **Text Renderer** for each of the Score Actors and select the Font in the Font area. We write 0 to the Text area. 106 | 107 | *Note : When the project becomes bigger, it is possible to duplicate Actors 108 | to avoid repetition, but as we are learning, repetition of this step is important.* 109 | 110 | We have everything inside the scene now. 111 | 112 | We need to set all the correct **positions** of the Actors to be able to see them 113 | nicely when we launch the game. 114 | 115 | We have already set the Position of the Actor Camera to (0, 0, 5), here the position 116 | of all the others Actors : 117 | 118 | * Background (0, 0, 0) 119 | * Ball (0, 0, 4) 120 | * Player1 (0, 0, 0) 121 | * Paddle (-3.7, 0, 4) 122 | * Score (-2, 2.5, 2) 123 | * Player2 (0, 0, 0) 124 | * Paddle (3.7, 0, 4) 125 | * Score (2, 2.5, 2) 126 | 127 | To finish we can change the color of each Score Actor, you can do so by clicking 128 | on the little checkbox next to the label **color** and choosing a color. 129 | Here the hexadecimal colors codes: 130 | 131 | * Player 1 – Score : f14735 132 | * Player 2 – Score : 356ef1 133 | 134 | 135 | Ok, now all the Actors are placed and we can launch the game to see if everything displays correctly. 136 | 137 | Right now, nothing will happen beside the display and it is normal. We need to start 138 | programming the logic of our game to have some interaction. However a last step before jumping in the code is to set the Physic Bodies to our sprites, we will need these solid bodies 139 | for the collision between the ball and the paddles. 140 | 141 | ### Setting Arcade Bodies 142 | 143 | In our Game Scene, we need to gave new component **Arcade Body 2D** to both of our Paddles and to our Ball. 144 | 145 | We then change for both paddles the size of x to 0.2 and keep y to 1 (size = 0.2, 1) and for the ball 146 | the size of x and y to 0.2 (size = 0.2, 0.2). Now the Arcade Body outlines are the same size as our sprites. 147 | 148 | In the game the illusion is than the **sprite ball will collide with the sprite paddle**, when 149 | in reality it is the **ball physic body and the paddles physic bodies** that will collide. 150 | The sprites are just attached to them and follow the movement of the body. 151 | 152 | ![arcadebody.png](img/arcadebody.png) 153 | 154 | Next: [Programming the game logic](ch3.md#chapter-3--programming-the-game-logic) 155 | 156 | [1]: http://docs.superpowers-html5.com/en/getting-started/about-superpowers 157 | [2]: https://github.com/mseyne/superpowers-sources/tree/master/1SuperPong 158 | -------------------------------------------------------------------------------- /1SuperPong/ch3.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #1 SUPER PONG 2 | ## *Chapter 3 : programming the game logic* 3 | 4 | ### Introduction 5 | 6 | If you don't know nothing about Programming in Javascript (which TypeScript is based on) 7 | you will maybe need to look at a tutorial to have a first idea (like the track JavaScript on Khan Academy). 8 | Don't worry, coding/programming is not as difficult as we could imagine, it is about 9 | solving real problems with logic and not much about abstraction. It is important to not 10 | go fast and take the time to understand basic concepts. You could feel it is really 11 | slow and abstract to get a really simple thing, but it will go faster and more concrete 12 | as you understand by practicing this steps (the frustration comes often when we try 13 | to do something we aren't prepared to do). 14 | 15 | However you can continue the tutorial and try to understand the basics by jumping in 16 | right now and copying the code below in Superpowers, by repetition and practice alone, 17 | exploring and testing, making mistakes and repairing them (which we call debugging, 18 | a fundamental concept in programming, where errors and mistakes are not to be avoid 19 | but understood) slowly but confidently, you will make amazing progress. 20 | 21 | ### Scripting the paddles behaviors 22 | 23 | We can now start to code the logic of our game, starting by implementing the movement of the paddles. 24 | 25 | In our folder GameScripts, we will start to code in the script Paddles. You will have a default script, but 26 | let's remove it all and write this template instead: 27 | 28 | ```TypeScript 29 | class Paddle1Behavior extends Sup.Behavior { 30 | 31 | update() { 32 | 33 | } 34 | } 35 | 36 | class Paddle2Behavior extends Sup.Behavior { 37 | 38 | update() { 39 | 40 | } 41 | } 42 | 43 | Sup.registerBehavior(Paddle1Behavior); 44 | Sup.registerBehavior(Paddle2Behavior); 45 | ``` 46 | 47 | We will work in the first Class Paddle1Behavior, and the second Class Paddle2Behavior will be almost the same. 48 | 49 | We define variables for the Class, **pad** which will be a connection to the body of our 50 | scene and a number **speed**. We write under the Class line : 51 | 52 | ```TypeScript 53 | class Paddle1Behavior extends Sup.Behavior { 54 | // connect the paddle body to a variable which will be used many times in the script 55 | pad = this.actor.arcadeBody2D; 56 | // set the speed of the paddle 57 | speed : number = 0.1; 58 | [...] 59 | ``` 60 | 61 | We will use a condition **if** and the command **Sup.Input** which is important in Superpowers 62 | to be able to get user input, which is the way player can have control in the game with 63 | the mouse, the keyboard, the gamepad or his fingers on mobile. 64 | 65 | We need a way to say to the sprite body to move if we press a keyboard key. We use the command 66 | setVelocityY (because for the paddle we will move only on the y axis). 67 | 68 | We also need to check if the paddle don't go out of the screen and we need to limit 69 | maximum and minimun until which the paddle can go up and down. To do so, we will 70 | keep track of the Y position of our paddle and add in our condition the possibility 71 | to move the paddle only if the y is under a maximum y and above a minimum y. 72 | 73 | In the **update** method we write our script as follow. 74 | 75 | ```TypeScript 76 | [...] 77 | update() { 78 | 79 | // get Y position of paddle in a variable 80 | let y : number = this.actor.getY() ; 81 | 82 | // if the key W is pressed and y < to max, the velocity of the body is set in motion with speed 83 | if(Sup.Input.isKeyDown("W") && y < 2.35){ 84 | this.pad.setVelocityY(this.speed); 85 | } 86 | // if the key S is pressed and y > to min, the velocity of the body is set in motion with negative speed 87 | else if(Sup.Input.isKeyDown("S") && y > -2.35){ 88 | this.pad.setVelocityY(-this.speed); 89 | } 90 | // in other situations the velocity of the body is set to 0 91 | else{ 92 | this.pad.setVelocityY(0); 93 | } 94 | 95 | } 96 | […] 97 | ``` 98 | 99 | To try our Behavior we need to attach it to our Actor in the Scene. In the Paddle of 100 | player1 create a new component **Behavior** and choose the class Paddle1Behavior. 101 | 102 | ![behavior.png](img/behavior.png) 103 | 104 | We can now start our program and we should be able to see the paddle moving up and 105 | down with the key W and S. 106 | 107 | We can copy our code into the Class Paddle2Behavior, but instead 108 | of W and S we change the Sup.Input.isKeyDown to 'UP' and 'DOWN'. We will use this for the second paddle. 109 | 110 | In the same way than before, we attach the script in the Scene Game to the paddle of player 2. 111 | (new component>Behavior), we choose the class Paddle2Behavior. 112 | 113 | 114 | ### Scripting the ball behavior 115 | 116 | We can now start to code the movements of the ball in the asset script Ball in the folder 117 | GameScripts. This Script will be contain more than the script paddles because we will 118 | write the collisions and the scores systems inside the same Class. 119 | 120 | We won't use the start method, so we can remove it from the default template. We should now have now as a starter script: 121 | 122 | ```TypeScript 123 | class BallBehavior extends Sup.Behavior { 124 | 125 | update() { 126 | 127 | } 128 | } 129 | Sup.registerBehavior(BallBehavior); 130 | ``` 131 | Because our ball will have acceleration we can now add the default/starting speed, which 132 | will be a constant that we will refer to. We put our constant outside of the main Class - 133 | in won't matter in this game, but in other games, this constant could be then used in 134 | other Classes of the game, so it is a good habit to get used to. 135 | 136 | ```TypeScript 137 | const BALLSPEED : number = 0.05 ; 138 | class BallBehavior extends Sup.Behavior { 139 | […] 140 | ``` 141 | 142 | We will now add our variables to BallBehavior: 143 | 144 | * one for the speed of the moving ball, starting from our constant BALLSPEED (it will accellerate) 145 | * one Array which contain both scores of player1 and player2 146 | * two variables that will be flags positive or 147 | negative which will give us the x/y direction of the ball (for example if the ball goes up and touches the up side of the game table, the variable will take the oposite value, telling us than the ball should move in the oposite direction) 148 | 149 | ```TypeScript 150 | […] 151 | class BallBehavior extends Sup.Behavior { 152 | // speed variable 153 | speed : number = BALLSPEED; 154 | // Connect the actor body to a constant 155 | ball = this.actor.arcadeBody2D; 156 | // An Array with score[0] for player 1 and score[1] for player 2 157 | score = [0, 0]; 158 | // set positive or negative direction variables of x and y 159 | dx : number = 1; dy : number = 1; 160 | […] 161 | ``` 162 | 163 | We write different conditions for different cases : 164 | 165 | 1. If the ball touches the up or down sides of the table, the ball will direction the on y axis and continue to move on the same x axis. We will check a condition and if true, change the variable **dy**. 166 | 167 | 2. If the ball collides with the paddles, the ball take a little acceleration and goes in 168 | the opposite direction on the x axis while stay the same on y axis (if the ball collides with left or right side of the paddle). 169 | We will use the **Sup.ArcadePhysics2D.collides** method to check if the Physics Bodies 170 | of the ball and the paddles collides together. We will also have a second check of the side of the ball collision with the method **getTouches**. 171 | 172 | 3. If the ball touches the right or left side (when the paddle misses the ball), a score is made for the player who made the goal (we will see this point a bit later), and the ball returns to the center and resumes with the default speed. 173 | 174 | Ok, now we write the behavior of the ball inside the loop method update. 175 | 176 | ```TypeScript 177 | […] 178 | update() { 179 | 180 | // get the ball position of x and y 181 | 182 | let x : number = this.actor.getX(); let y : number = this.actor.getY(); 183 | 184 | // change direction of y if ball reach up and down sides 185 | 186 | if(y > 2.85 || y < -2.85){ 187 | this.dy = this.dy * -1; 188 | } 189 | 190 | // We check if there is collision between the ball and the two paddles 191 | 192 | if(Sup.ArcadePhysics2D.collides(this.ball, Sup.ArcadePhysics2D.getAllBodies())){ 193 | 194 | /* If there is collision we check if the ball touch 195 | a left or right side (we change the x direction) or 196 | the up and down side of the paddles (y direction), 197 | the speed of the ball take an acceleration. */ 198 | 199 | if(this.ball.getTouches().right || this.ball.getTouches().left){ 200 | this.dx = this.dx * -1; 201 | this.speed += 0.01 202 | } 203 | else { 204 | this.dy = this.dy * -1; 205 | } 206 | 207 | } 208 | 209 | /* We check if the ball pass the paddle and go beyond 210 | (sides left and right of the game table) if yes, we move 211 | the ball to the center and change the direction of x axis, 212 | the speed take the default speed. */ 213 | 214 | if(x > 4 || x < -4){ 215 | this.ball.warpPosition(0, 0); 216 | this.dx = this.dx * -1; 217 | this.speed = BALLSPEED; 218 | } 219 | 220 | // set ball movement velocity speed*direction for x and y axis (the ball stay in movement at any time) 221 | 222 | this.ball.setVelocity(this.speed*this.dx, this.speed*this.dy); 223 | 224 | } 225 | […] 226 | ``` 227 | 228 | 229 | To see the script in action we need to do the same thing we did to test the paddles: attach the 230 | script Ball in the Scene Game to the Actor Ball. (new component>behavior, class = BallBehavior) 231 | 232 | We should have now a game working nicely. 233 | 234 | ### Scoring system 235 | 236 | We will now add a simple scoring system. What we want is that player who gets the ball in the other camp scores one point (the score increments by one). 237 | 238 | To do so, we add two conditions at the end of our class BallBehavior, one for the 239 | case that the ball touches the side of player 1 and one for the side of player 2. 240 | 241 | We use then the **textRenderer** method to change the display of the score that we connect with 242 | the method getActor('Player1').getChild('Score') for the score of player 1 and getActor('Player2').getChild('Score') 243 | for the score of player 2. 244 | 245 | ```TypeScript 246 | [...] 247 | //We change the score depending on which side the ball go on x axis 248 | if(x > 4){ 249 | ++this.score[0]; 250 | Sup.getActor("Player1").getChild("Score").textRenderer.setText(this.score[0]); 251 | } 252 | 253 | if(x < -4){ 254 | ++this.score[1]; 255 | Sup.getActor("Player2").getChild("Score").textRenderer.setText(this.score[1]); 256 | } 257 | 258 | // set ball movement velocity speed*direction for x and y axis (the ball stay in movement at any time) 259 | this.ball.setVelocity(this.speed*this.dx, this.speed*this.dy); 260 | } 261 | } 262 | Sup.registerBehavior(BallBehavior); 263 | ``` 264 | 265 | The game logic is finished, we just need to polish it a bit before release - but now we have our game. 266 | 267 | Next: [Polishing the game](ch4.md#chapter-4--polishing-the-game) 268 | -------------------------------------------------------------------------------- /1SuperPong/ch4.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #1 SUPER PONG 2 | ## *Chapter 4: Polishing the game* 3 | 4 | 5 | ### Build the Menu Structure and load assets files 6 | 7 | We are going to build a Menu, it is the place where the player comes when the game 8 | is launched for the first time. It is an opportunity for us to build a simple button 9 | in Superpowers and have a glimpse of Mouse control and interaction. 10 | 11 | We start by adding assets to the structure of our program, we add one Scene that 12 | we name Menu, We add the sprites and load the image files for each. We also add a 13 | new script, button, than we will use for the code of our Menu. 14 | 15 | * Menu (Startup Scene) 16 | * MenuSprites 17 | * title 18 | * starton 19 | * startoff 20 | * MenuScripts 21 | * button 22 | 23 | In Settings, we change the start up screen from Game to Menu, this way the game 24 | will start with the Menu Scene we are building. We have now this structure : 25 | 26 | ![menustructure.png](img/menustructure.png) 27 | 28 | When we load sprites assets we need to do a setup for each to give the grid size 29 | the same as the image size. It will also set the origin to the center, which will be 30 | important to positioning the sprites in the scene later. 31 | 32 | 33 | ### Build the Menu Scene 34 | 35 | We are going to combine assets together in a way to have this screen when we launch the game: 36 | 37 | ![menu.png](img/menu.png) 38 | 39 | We start by creating an Actor Camera with a new component **Camera**. We also create two 40 | actors with the component **sprite renderer**, (Title, Button) and two actors with 41 | the component **text renderer**. (Info1 and Info2). 42 | 43 | Here the properties for each actors : 44 | 45 | * Camera, position (0, 0, 4), mode = Orthigraphic, Orthographic Scale = 3 (the camera should fit with the image title.png) 46 | * Title, position(0, 0, 0), sprite = MenuSprites/title 47 | * Button, position(0, -1, 2), sprite = MenuSprites/startoff 48 | * Info1, position(-1.2, -1, 0), Text = Player 1 W / S, color = f14735 , size = 32 49 | * Info2, position(1.2, -1, 0), Text = Player 2 Up / Down, color = 356ef1 , size = 32 50 | 51 | We have now our menu with game instructions. We only need to have a functional button. We start programming it. 52 | 53 | 54 | ### Scripting a Button 55 | 56 | We now add the variables we will need for our script, the important part is 57 | the Math.Ray function that we need to locate the intersection between the mouse and the game screen. 58 | 59 | ```TypeScript 60 | // We initialize a global variable ray which is of type Math.Ray 61 | var ray : Sup.Math.Ray; 62 | 63 | class ButtonBehavior extends Sup.Behavior { 64 | // flag to tell when the mouse hover the button 65 | isHover : boolean = false; 66 | 67 | awake() { 68 | ray = new Sup.Math.Ray(this.actor.getPosition(), new Sup.Math.Vector3(0, 0, -1)); 69 | } 70 | [...] 71 | ``` 72 | 73 | We next define a function mouse inside the class ButtonBehavior. We change the 74 | sprites of an actor by calling the method spriteRenderer.setSprite(spritepath) to 75 | this actor. We load the scene Game when the button is clicked with Sup.loadScene(SceneName). 76 | 77 | ```TypeScript 78 | /* We define different possible actions of the mouse, 79 | click action load the game scene, hovering and unhovering 80 | make the button to change the sprite.*/ 81 | 82 | mouse(action) { 83 | if(action == "click"){ 84 | Sup.loadScene("Game"); 85 | } 86 | else if(action == "hover"){ 87 | Sup.getActor("Button").spriteRenderer.setSprite("MenuSprites/starton"); 88 | } 89 | else if(action == "unhover"){ 90 | Sup.getActor("Button").spriteRenderer.setSprite("MenuSprites/startoff"); 91 | } 92 | } 93 | ``` 94 | 95 | And finally in the update loop, we build the logic of our button with checking conditions. 96 | We update our variable ray at each frame to have the position of the mouse in the screen. 97 | We check the intersection of the mouse and the button with the method intersectActor. 98 | For each condition, if the check is true, the function mouse(action) is called. 99 | 100 | ```TypeScript 101 | update() { 102 | // Refresh position of the mouse in the camera 103 | ray.setFromCamera(Sup.getActor("Camera").camera, Sup.Input.getMousePosition()); 104 | 105 | /* Condition to check if yes or no, the mouse hover 106 | the button, and if yes, check if the mouse click. 107 | We call the mouse function with the action related. */ 108 | 109 | if(ray.intersectActor(this.actor, false).length > 0){ 110 | if(!this.isHover){ 111 | this.mouse("hover"); 112 | this.isHover = true; 113 | } 114 | if(Sup.Input.wasMouseButtonJustPressed(0)){ 115 | this.mouse("click") 116 | } 117 | } 118 | else if(this.isHover){ 119 | this.isHover = false; 120 | this.mouse("unhover") 121 | } 122 | 123 | } 124 | } 125 | Sup.registerBehavior(ButtonBehavior); 126 | ``` 127 | 128 | Before starting the game, we need to attach our script by adding a new component 129 | behavior to the actor Button and choose the class ButtonBehavior. 130 | 131 | We now have a menu and an almost finished game, we can now give sounds and a possibility to end the game. 132 | 133 | 134 | ### Adding Sound 135 | 136 | We have already loaded the sound assets, we are going to incorporate them inside our game. 137 | 138 | But before returning to the Game scripts, we can add one sound to the menu when 139 | we click to start the game by simply writing one line of code with the method **Audio.playsound**. 140 | 141 | ```TypeScript 142 | [...] 143 | if(action == "click"){ 144 | Sup.loadScene("Game"); 145 | Sup.Audio.playSound("GameSounds/toc"); 146 | } 147 | […] 148 | ``` 149 | 150 | We want a sound when the ball touches a side or the paddle, and we also want a sound when there is a goal. We then go in the Ball script and add Audio sounds in the conditions of update : 151 | 152 | ```TypeScript 153 | […] 154 | if(y > 2.85 || y < -2.85){ 155 | Sup.Audio.playSound("GameSounds/tac"); 156 | […] 157 | 158 | […] 159 | if(Sup.ArcadePhysics2D.collides(this.ball, Sup.ArcadePhysics2D.getAllBodies())){ 160 | Sup.Audio.playSound("GameSounds/toc"); 161 | […] 162 | 163 | […] 164 | if(x > 4 || x < -4){ 165 | Sup.Audio.playSound("GameSounds/tada"); 166 | […] 167 | ``` 168 | 169 | The game now has sounds. 170 | 171 | ### Adding an end to the game 172 | 173 | To polish the game we could create a new scene victory which displays the player 174 | who won on the screen. We would need to load this scene when the number of point reach a maximum. 175 | But here, we will just return to the menu screen when one player get 10 points. 176 | 177 | We need to add a condition at the end of the loop update. 178 | 179 | ```TypeScript 180 | […] 181 | if(this.score[0] == 10 || this.score[1] == 10){ 182 | Sup.loadScene("Menu"); 183 | } 184 | […] 185 | ``` 186 | 187 | The game is now complete. 188 | 189 | Finally: [Complete Game Source Reference](ch5.md#chapter-5--complete-game-source-reference) 190 | -------------------------------------------------------------------------------- /1SuperPong/ch5.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #1 SUPER PONG 2 | ## *Chapter 5 : Complete Game Source Reference* 3 | 4 | ### Final considerations about the learning process of making video games 5 | 6 | When you finish this tutorial, you maybe think that you still don't know how to make a Pong game, 7 | than you just copied what the tutorial said. Well, you are not really right and you are not really wrong. 8 | If you understood the steps a bit, then take a deep breath, believe in yourself and 9 | start to remake it from scratch without looking the tutorial. Remaking it again using your own memory 10 | will help you to deeper the steps in your memory and have a clearer understanding of what you are doing. 11 | 12 | Following the tutorial with little understanding is the first step of the learning process 13 | and as your adventure continues and your exploration go deeper, you need now to face alone a blank project 14 | and start the Pong game again. If you are stuck, you can peek at the tutorial for clues and continue by yourself. 15 | It is good to use the tutorial like documentation or a reference because what is important is that you can 16 | understand each step and go through them by yourself as you understand the whole process more. 17 | All the other games from the next tutorial and building games in the future is just about adding complexity 18 | and features, slowly crafting worlds that player can explore and have fun with. 19 | 20 | Even if you have built Pong and understood the steps without looking too much at the tutorial, you maybe don't know what to do next. 21 | If you want to go to the next step of your adventure in game development, you can start to build a new game 22 | on your own, read code of others or follow another tutorial and follow it until you understand it. And continue 23 | this way until you feel comfortable enough with Superpowers and programming with TypeScript. 24 | 25 | The purpose of this series is to propose more complex tutorials as we advance to help us to learn how to climb this big mountain 26 | until we feel strong enough to build by ourselves this fantastic, challenging and emotionally rich video games. 27 | 28 | See you soon if you decide you want to continue the adventure of game development with Superpowers. 29 | 30 | 31 | ### Complete Game Source Reference : 32 | 33 | **Assets files of this game :** 34 | 35 | * background.png 36 | * pad1.png 37 | * pad2.png 38 | * ball.png 39 | * tac.mp3 40 | * toc.mp3 41 | * tada.mp3 42 | * menu/title.png 43 | * menu/starton.png 44 | * menu/startoff.png 45 | * scripts/ball.ts 46 | * scripts/paddles.ts 47 | 48 | **Project Assets Structure :** 49 | 50 | Menu (Startup Scene) 51 | * MenuSprites 52 | * title 53 | * starton 54 | * startoff 55 | * MenuScripts 56 | * button 57 | Game 58 | * GameSprites 59 | * background 60 | * pad1 61 | * pad2 62 | * ball 63 | * GameScripts 64 | * ball 65 | * paddles 66 | * GameSounds 67 | * tac 68 | * toc 69 | * tada 70 | Font 71 | 72 | **Scripts Sources :** 73 | 74 | * ball.ts 75 | 76 | ```TypeScript 77 | const BALLSPEED : number = 0.05 ; 78 | 79 | class BallBehavior extends Sup.Behavior { 80 | // Connect the actor body to a constant 81 | ball = this.actor.arcadeBody2D; 82 | // An Array with score[0] for player 1 and score[1] for player 2 83 | score = [0, 0]; 84 | // speed variable 85 | speed : number = BALLSPEED; 86 | // set positive or negative direction variables of x and y 87 | dx : number = 1; dy : number = 1; 88 | 89 | update() { 90 | // get the ball position of x and y 91 | let x : number = this.actor.getX(); let y : number = this.actor.getY(); 92 | 93 | // change direction of y if ball reach up and down sides 94 | if(y > 2.85 || y < -2.85){ 95 | Sup.Audio.playSound("GameSounds/toc"); 96 | this.dy = this.dy * -1; 97 | } 98 | 99 | // We check if there is collision between the ball and the two paddles 100 | if(Sup.ArcadePhysics2D.collides(this.ball, Sup.ArcadePhysics2D.getAllBodies())){ 101 | Sup.Audio.playSound("GameSounds/tac"); 102 | /* If there is collision we check if the ball touch 103 | a left or right side (we change the x direction) or 104 | the up and down side of the paddles (y direction), 105 | the speed of the ball take an acceleration. */ 106 | if(this.ball.getTouches().right || this.ball.getTouches().left){ 107 | this.dx = this.dx * -1; 108 | this.speed += 0.01 109 | } 110 | else { 111 | this.dy = this.dy * -1; 112 | } 113 | } 114 | 115 | /* We check if the ball pass the paddle and go beyond 116 | (sides left and right of the game table) if yes, we move 117 | the ball to the center and change the direction of x axis, 118 | the speed take the default speed. */ 119 | if(x > 4 || x < -4){ 120 | this.ball.warpPosition(0, 0); 121 | this.dx = this.dx * -1; 122 | this.speed = BALLSPEED; 123 | Sup.Audio.playSound("GameSounds/tada"); 124 | } 125 | 126 | //We change the score depending on which side the ball go on x axis 127 | if(x > 4){ 128 | ++this.score[0]; 129 | Sup.getActor("Player1").getChild("Score").textRenderer.setText(this.score[0]); 130 | } 131 | 132 | if(x < -4){ 133 | ++this.score[1]; 134 | Sup.getActor("Player2").getChild("Score").textRenderer.setText(this.score[1]); 135 | } 136 | 137 | // set ball movement velocity speed*direction for x and y axis (the ball stay in movement at any time) 138 | this.ball.setVelocity(this.speed*this.dx, this.speed*this.dy); 139 | 140 | if(this.score[0] == 10 || this.score[1] == 10){ 141 | Sup.loadScene("Menu"); 142 | } 143 | } 144 | } 145 | Sup.registerBehavior(BallBehavior); 146 | ``` 147 | 148 | * paddles.ts 149 | 150 | ```TypeScript 151 | class Paddle1Behavior extends Sup.Behavior { 152 | // connect the paddle body to a variable which will be used many times in the script 153 | pad = this.actor.arcadeBody2D; 154 | 155 | // set the speed of the paddle 156 | speed : number = 0.1; 157 | 158 | update() { 159 | // get Y position of paddle in a variable 160 | let y : number = this.actor.getY(); 161 | 162 | // if the key is pressed and y < to max, the velocity of the body is set in motion with speed 163 | if(Sup.Input.isKeyDown("W") && y < 2.35){ 164 | this.pad.setVelocityY(this.speed); 165 | } 166 | // if the key is pressedand y > to min, the velocity of the body is set in motion with negative speed 167 | else if(Sup.Input.isKeyDown("S") && y > -2.35){ 168 | this.pad.setVelocityY(-this.speed); 169 | } 170 | // in other situations the velocity of the body is set to 0 171 | else{ 172 | this.pad.setVelocityY(0); 173 | } 174 | } 175 | } 176 | 177 | class Paddle2Behavior extends Sup.Behavior { 178 | // connect the paddle body to a variable which will be used many times in the script 179 | pad = this.actor.arcadeBody2D; 180 | 181 | // set the speed of the paddle 182 | speed : number = 0.1; 183 | 184 | update() { 185 | // get Y position of paddle in a variable 186 | let y : number = this.actor.getY(); 187 | 188 | // if the key is pressed and y < to max, the velocity of the body is set in motion with speed 189 | if(Sup.Input.isKeyDown("UP") && y < 2.35){ 190 | this.pad.setVelocityY(this.speed); 191 | } 192 | // if the key is pressedand y > to min, the velocity of the body is set in motion with negative speed 193 | else if(Sup.Input.isKeyDown("DOWN") && y > -2.35){ 194 | this.pad.setVelocityY(-this.speed); 195 | } 196 | // in other situations the velocity of the body is set to 0 197 | else{ 198 | this.pad.setVelocityY(0); 199 | } 200 | } 201 | } 202 | 203 | Sup.registerBehavior(Paddle1Behavior); 204 | Sup.registerBehavior(Paddle2Behavior); 205 | ``` 206 | 207 | * button.ts 208 | 209 | ```TypeScript 210 | // We initialize a variable ray which is of type Math.Ray 211 | var ray : Sup.Math.Ray; 212 | 213 | class ButtonBehavior extends Sup.Behavior { 214 | // flag to tell when the mouse hover the button 215 | isHover : boolean = false; 216 | 217 | awake() { 218 | ray = new Sup.Math.Ray(this.actor.getPosition(), new Sup.Math.Vector3(0, 0, -1)); 219 | } 220 | 221 | /* We define different possible actions of the mouse, 222 | click action load the game scene, hovering and unhovering 223 | make the button to change the sprite.*/ 224 | 225 | mouse(action) { 226 | if(action == "click"){ 227 | Sup.loadScene("Game"); 228 | Sup.Audio.playSound("GameSounds/toc"); 229 | } 230 | else if(action == "hover"){ 231 | Sup.getActor("Button").spriteRenderer.setSprite("MenuSprites/starton"); 232 | } 233 | else if(action == "unhover"){ 234 | Sup.getActor("Button").spriteRenderer.setSprite("MenuSprites/startoff"); 235 | } 236 | } 237 | 238 | update() { 239 | // Refresh position of the mouse in the camera 240 | ray.setFromCamera(Sup.getActor("Camera").camera, Sup.Input.getMousePosition()); 241 | 242 | /* Condition to check if yes or no, the mouse hover 243 | the button, and if yes, check if the mouse click. 244 | We call the mouse function with the action related. */ 245 | 246 | if(ray.intersectActor(this.actor, false).length > 0){ 247 | if(!this.isHover){ 248 | this.mouse("hover"); 249 | this.isHover = true; 250 | } 251 | if(Sup.Input.wasMouseButtonJustPressed(0)){ 252 | this.mouse("click") 253 | } 254 | } 255 | else if(this.isHover){ 256 | this.isHover = false; 257 | this.mouse("unhover") 258 | } 259 | 260 | } 261 | } 262 | Sup.registerBehavior(ButtonBehavior); 263 | ``` 264 | -------------------------------------------------------------------------------- /1SuperPong/img/actors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/actors.png -------------------------------------------------------------------------------- /1SuperPong/img/arcadebody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/arcadebody.png -------------------------------------------------------------------------------- /1SuperPong/img/behavior.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/behavior.png -------------------------------------------------------------------------------- /1SuperPong/img/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/camera.png -------------------------------------------------------------------------------- /1SuperPong/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/menu.png -------------------------------------------------------------------------------- /1SuperPong/img/menustructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/menustructure.png -------------------------------------------------------------------------------- /1SuperPong/img/pong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/pong.png -------------------------------------------------------------------------------- /1SuperPong/img/settingassets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/settingassets.png -------------------------------------------------------------------------------- /1SuperPong/img/spriteset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/1SuperPong/img/spriteset.png -------------------------------------------------------------------------------- /2SuperOXO/README.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Learn game development with a Tic Tac Toe game* 3 | 4 | ### Summary 5 | 6 | 1. [Introduction](ch1.md#chapter-1--introduction) 7 | * [Planning the game](ch1.md#planning-the-game) 8 | * [MVG Features](ch1.md#mvg-features) 9 | 2. [Building the Game Structure](ch2.md#chapter-2-building-the-game-structure) 10 | * [Loading the game assets](ch2.md#loading-game-assets) 11 | * [Building the game scene](ch2.md#building-the-game-scene) 12 | 3. [Writing the Player actions, Game logic part 1](ch3.md#chapter-3--writing-the-player-actions-game-logic-part-1) 13 | * [Setting the global script](ch3.md#setting-the-global-script) 14 | * [Scripting th mouse behavior](ch3.md#scripting-the-mouse-behavior) 15 | * [A turn based game](ch3.md#a-turn-based-game) 16 | 4. [Writing the Computer response, Game logic part 2](ch4.md#chapter-4--writing-the-computer-response-game-logic-part-2) 17 | * [Algorithm of a simple computer AI](ch4.md#algorithm-of-a-simple-computer-ai) 18 | * [Scripting the computer behavior](ch4.md#scripting-the-computer-behavior) 19 | * [Scripting the victory checking](ch4.md#scripting-the-victory-checking) 20 | 5. [Polishing the Game](ch5.md#chapter-5-polishing-the-game) 21 | * [Create a victory screen](ch5.md#create-a-victory-screen) 22 | * [Restart the game](ch5.md#restart-the-game) 23 | * [Randomize the game starting](ch5.md#randomize-the-game-starting) 24 | * [Slowing down the game](ch5.md#slowing-down-the-game) 25 | 6. [Complete Game Source Reference](ch6.md#chapter-6-complete-game-source-reference) 26 | * [Assets files](ch6.md#assets-files) 27 | * [Project structure](ch6.md#project-structure) 28 | * [Scripts sources](ch6.md#scripts-sources) 29 | 30 | ### What we will learn in this tutorial 31 | 32 | - Set up assets and build a scene for a Tic Tac Toe in Superpowers 33 | - Write a mouse behavior for the player to choose how to play by clicking on an object 34 | - Write functions inside a namespace to access it later from our others scripts 35 | - Write a simple AI that check a game situation to make responds accordingly 36 | - Superpowers and TypeScript API methods used: 37 | - Sup.Math.Ray() 38 | - Math.floor and Math.random 39 | - Sup.Actor() 40 | - Actor.addBehavior() 41 | - Sup.getActor().destroy() 42 | - Sup.setTimeout() 43 | -------------------------------------------------------------------------------- /2SuperOXO/ch1.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Chapter 1 : Introduction* 3 | 4 | ### Planning the Game 5 | 6 | We are going to make a [Tic Tac Toe][1] game, it is a very simplistic strategy 7 | game that is a perfect project for introducing video game development. 8 | 9 | ![tictactoe.png](img/tictactoe.png) 10 | 11 | The game is turn based on a 3x3 squares grid, the player and the computer try to 12 | align 3 cross or 3 circle to win. 13 | 14 | This tutorial is more a programming tutorial than was the previous one (pong) but 15 | the powerful tool of Scene from Superpowers will still be very useful to help us to 16 | build the main graphical structure without the need to think much about it during our code writing. 17 | 18 | One of the super power of superpowers is to allow us to separate clearly what is 19 | the game logic from what is the visual elements of our game, we will use this ability. 20 | 21 | 22 | ### MVG Features 23 | 24 | Here the minimum viable game (MVG) features we want : 25 | 26 | * Player is cross, Computer is circle. 27 | * A grid 3x3 where each square can be empty, cross or circle. 28 | * The player can control the mouse to choose which square to play. 29 | * A simple computer artificial intelligence checking the game board to decide how to play against the player. 30 | * A function that find out if the player or the computer won, or if the game is a tie. 31 | 32 | ***Note about the video game history*** *: Built in 1952* [OXO][2] *is the first graphical 33 | known video game, the game was not made public and was only intended for academic research purposes.* 34 | 35 | [1]: https://en.wikipedia.org/wiki/Tic-tac-toe 36 | [2]: https://en.wikipedia.org/wiki/OXO 37 | -------------------------------------------------------------------------------- /2SuperOXO/ch2.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Chapter 2 : Building the Game Structure* 3 | 4 | Before to write the game logic, we will use Superpowers to prepare everything we will need for our game to display correctly. 5 | 6 | After launching our server, we create a new Project for our game. 7 | 8 | 9 | ### Loading game assets 10 | 11 | We start to create a **new Scene** called **Game** and set it up as the **Startup Scene** in the **Settings**. 12 | 13 | Also, in the Settings we can give the **Screen ratio** of our game to 300 for x and 300 for y. 14 | 15 | We build the assets structure by creating two folders, one for the **Sprites** and an other for the **Scripts**. 16 | 17 | We create next the **new assets** : 3 Sprites and 2 Scripts. Here the structure of our game : 18 | 19 | * Game (Startup Scene) 20 | * Sprites 21 | * Symbols 22 | * Screens 23 | * Board 24 | * Scripts 25 | * Global 26 | * Player 27 | 28 | 29 | ![assets.png](img/assets.png) 30 | 31 | 32 | For each of our sprites asset we upload the img file related (you can find them in 33 | this [source repository](https://github.com/mseyne/superpowers-sources/tree/master/2SuperOXO)) 34 | and we set the grid with setup. 35 | 36 | For the Symbols and Screens Sprites we create new Animations which will only be frames we 37 | use to select when we need them. For **Symbols** Sprite we have four differents frames: 38 | unHover, isHover, cross and circle. We use animation with only 1 frame each as we use 39 | this tool to switch from one to an other in the game but we won't use animated sprites as a feature. 40 | 41 | 42 | ![symbols.png](img/symbols.png) 43 | 44 | 45 | We do the same with the **Screens** Sprite, we have 3 frames, cross, circle and tie. 46 | 47 | 48 | ![screens.png](img/screens.png) 49 | 50 | 51 | We have now this configuration for each sprites : 52 | 53 | * **Symbols**, file : symbols.png, size : 96 x 384 , grid : 96 x 96 54 | * Animations : 55 | * unHover (0,0,1) 56 | * isHover (1,1,1) 57 | * cross (2,2,1) 58 | * circle (3,3,1) 59 | * **Screens**, file : screens.png, size : 300 x 900, grid : 300 x 300 60 | * Animations : 61 | * cross (0,0,1) 62 | * circle (1,1,1) 63 | * tie (2,2,1) 64 | * **Board**, file : board.png, size : 300 x 300, grid : 300 x 300 65 | 66 | 67 | ### Building the game scene 68 | 69 | In the Game Scene, we start by creating a **new Actor Camera** with a **new component Camera**. 70 | 71 | We set the **position** of Camera to (x = 0, y = 0, z = 6), all the others actors 72 | will be placed in relation to this origin x = 0 and y = 0 and placed on z before 6. 73 | (After 6, the Actors are out of the camera focus). 74 | 75 | We create a **new Actor Board** with a **new component Sprite Renderer** with the Sprite 76 | (path) Sprites/Board. We check the position of Board is to (0, 0, 0). 77 | 78 | In the Camera Actor we change the **Camera Mode** to Orthographic and the **Orthographic 79 | Scale** to 3. (To fit with the Board Sprite) 80 | 81 | 82 | ![camera.png](img/camera.png) 83 | 84 | 85 | We now need to set the 9 **new Actor Squares** of the game and place each of them on the 86 | board grid. These actors will be the one which interact with the game logic. 87 | 88 | The Board Actor will be the parent and background and all the Squares Actors will 89 | be to a z position in front of the Board. 90 | 91 | We create a first **new Actor Square4** (the center square) and check its position to (0, 0, 2) and we give 92 | a new component Sprite Renderer with the Sprite Sprites/Symbols. (The first frame 93 | unHover is a little grey square that fit right in the board squares) 94 | 95 | We duplicate it 8 times (Ctrl + D), changing the names accordingly to have **Square0** to **Square8**. 96 | 97 | *Note : Because in Javascript and in many other programming language, we start to count to 0, we will 98 | approach the squares names the same way starting from Square0, Square1, Square2, etc.* 99 | 100 | We have now to set the position of each square, starting from Square0 to the up 101 | left and the Square8 to the down right position. Here the correct positions (x, y, z) 102 | for each Square to fit in the board. 103 | 104 | * Square0 (-1, 1, 2) 105 | * Square1 (0, 1, 2) 106 | * Square2 (1, 1, 2) 107 | * Square3 (-1, 0, 2) 108 | * Square4 (0, 0, 2) 109 | * Square5 (1, 0, 2) 110 | * Square6 (-1, -1, 2) 111 | * Square7 (0, -1, 2) 112 | * Square8 (1, -1, 2) 113 | 114 | We have now a complete Scene. 115 | 116 | 117 | ![board.png](img/board.png) 118 | 119 | 120 | We can also switch to the **3D mode** of the camera and run the game to check if everything is 121 | in place and work fine before to jump in the code. 122 | 123 | 124 | ![3D.png](img/3D.png) 125 | -------------------------------------------------------------------------------- /2SuperOXO/ch3.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Chapter 3 : Writing the Player actions, Game logic part 1* 3 | 4 | 5 | Our game have a structure and we know the features we want to add. 6 | 7 | First we will code the behavior of the mouse with the squares on the game board, 8 | however we need before that to create a constant that will hold the Squares. 9 | 10 | We have created two Scripts, **Global** and **Player**. 11 | 12 | * Global will contain all our main functions and global variables. 13 | * Player will contain all functions which is relative to the mouse behavior and the player turn. 14 | 15 | 16 | ### Setting the global script 17 | 18 | We have already built the board and placed the squares, we need now a way to access 19 | to each of this squares through the program. For this we will build a **SQUARES constant Array** 20 | which will be a container for our board datas and an important indicator 21 | about the state of all the squares of the board. 22 | 23 | *Note : an Array is a list of data in JavaScript.* 24 | 25 | We initialize the constant SQUARES by writing in the beginning of our Global script : 26 | 27 | ```TypeScript 28 | // Initialize globally the SQUARES Array 29 | const SQUARES = new Array; 30 | ``` 31 | 32 | For now, SQUARES is only an empty Array, we need to fill it with the nine squares 33 | to be able to access it at any time. 34 | 35 | We have before separated each square as individuals Actors. Instead of adding them 36 | one by one in the Array, we will use a loop that will **push 9 times** the 9 differents 37 | squares inside our SQUARES array and we will do so in a way that the index number 38 | in the array is in the same order that the name of each square (and it is why we 39 | started with Square0), then when we want to access to the first square of the board 40 | (up left corner) we use the square index that is in his name, in our example SQUARES[0] 41 | for Square0. 42 | 43 | In fact, we are going to push arrays inside the main Array SQUARES, like 9 times 44 | little list of two datas that we will access by using their **index numbers between brackets**. 45 | 46 | Each of the nine arrays will represent one square and be composed of : 47 | 48 | * **SQUARES [SquareIndex] [0]** : The SquareX Actor (by which we access to modify 49 | the Sprite), X being the index number. 50 | 51 | * **SQUARES [SquareIndex] [1]** : The current square situation (if it's hover or not, 52 | if there is a cross or circle played on it.) 53 | 54 | We are going to write the loop and function to set the squares arrays in a function 55 | inside the Global script and we are going to encapsulate our function (and the next one) 56 | in a **namespace Game{}**. This way we could call our (exported) function from anywhere in 57 | our others scripts calling **Game.theFunction()**, it make the code cleaner and it is a good 58 | habit to have our functions out of the global scope to avoid overwriting and conflict 59 | between functions that could be elsewhere. 60 | 61 | Here the commented loop function **setSquares** in the Game namespace wrote to the 62 | following of the global variables : 63 | 64 | ```TypeScript 65 | [...] 66 | namespace Game{ 67 | 68 | // define and export the function setSquare 69 | export function setSquares(){ 70 | 71 | /* 72 | Build the SQUARES array with arrays 73 | 0 : the Square Actor of current index 74 | 1 : default string value of situation : "unHover" 75 | */ 76 | 77 | // define a local addSquare function 78 | function addSquare(index){ 79 | // get the name of current square 80 | let name = "Square" + index.toString(); 81 | 82 | // get the actor from the Game scene 83 | let square = Sup.getActor("Board").getChild(name); 84 | 85 | // push the square array in SQUARES array to the next index 86 | SQUARES.push([square, "unHover"]); 87 | } 88 | 89 | // loop calling 9 times the function addSquare with the square index as parameter 90 | for(let i = 0; i < 9; i++){ 91 | addSquare(i); 92 | } 93 | } 94 | 95 | } 96 | ``` 97 | We cannot use our new setSquare() function if we call it directly in the Global 98 | script, we need to call it after the Game Scene is loaded in the game because the 99 | function use the Actors that are loaded with the Game Scene. 100 | 101 | To do this correctly, in Game Scene we add a **new component Behavior** to the Board Actor. 102 | We attach then attach the **class PlayerBehavior** from our script Player. 103 | 104 | 105 | ![behavior.png](img/behavior.png) 106 | 107 | 108 | This PlayerBehavior class will initialize (awake) after the scene is loaded, we 109 | will call our Game function from here. 110 | 111 | We go in the script Player and we write in the default template the call or our 112 | function setSquare in the awake method : 113 | 114 | ```TypeScript 115 | class PlayerBehavior extends Sup.Behavior { 116 | awake() { 117 | //Call the function setSquares which build the SQUARES Array 118 | Game.setSquares(); 119 | } 120 | 121 | update() { 122 | 123 | } 124 | } 125 | Sup.registerBehavior(PlayerBehavior); 126 | ``` 127 | 128 | If we start the game, nothing appear, it is normal as we just made a behind the 129 | scene Array. If we want to check than our Array SQUARES is made, we can add, 130 | after the call of your function, Sup.log(SQUARES); that will log in the console 131 | the 9 arrays of 2 datas for each. 132 | 133 | 134 | ### Scripting the Mouse Behavior 135 | 136 | First thing to do is to initialize inside the Global script our raycasting variable 137 | than we call **ray**. This will be required to detect intersection between the mouse and the Game Scene actors. 138 | 139 | ```TypeScript 140 | // Initialize globally the ray casting 141 | var ray = new Sup.Math.Ray(); 142 | [...] 143 | ``` 144 | 145 | To start to write the behavior of the mouse, we need to write a new method that 146 | check the differents actions that the player can obtain in the game by using the mouse. 147 | 148 | In the PlayerBehavior class of the Player script, we create the method **mouse** which 149 | take an **action** and the **square** on which this action apply as a parameter and we 150 | build the complete checking if, else if conditions, the directionals branchs that 151 | will take the program according to the parameter action. 152 | 153 | ```TypeScript 154 | [...] 155 | awake() { 156 | [...] 157 | } 158 | 159 | // mouse method receiving parameters of the player action and the square related to this action 160 | mouse(action, square){ 161 | if(action == "isHover"){ 162 | Sup.log("The mouse is over the" + square.getName()); // temporary log; 163 | } 164 | else if(action == "unHover"){ 165 | Sup.log("The mouse is out from the" + square.getName()); // temporary log; 166 | 167 | } 168 | else if(action == "click"){ 169 | Sup.log("The mouse click the" + square.getName()); // temporary log; 170 | } 171 | } 172 | 173 | [...] 174 | ``` 175 | 176 | *Note : The 3 Sup.log() ; are used to check our method, we will replace them afterwards.* 177 | 178 | We can't test our game right now, we need to write first the conditions that check 179 | the behavior of the mouse before to call the mouse method. 180 | 181 | In our **update loop**, we will now refresh constantly the ray caster and the mouse 182 | position in the screen and check each time if something changed. We set the conditions 183 | that will check the differents behaviors of the mouse and if one behavior fill one 184 | of the conditions, we will update the square situation in the array and ask to 185 | execute the mouse method by sending in the datas related to this behavior. 186 | 187 | We write our update loop  : 188 | 189 | ```TypeScript 190 | [...] 191 | update() { 192 | // Refresh the ray casting to the mouse position inside the camera screen 193 | ray.setFromCamera(Sup.getActor("Camera").camera, Sup.Input.getMousePosition()); 194 | 195 | // Create a new empty variable that will as value the differents array of the SQUARES constant 196 | let array; 197 | 198 | /* 199 | We loop through all the arrays in SQUARES and give the current array to the square variable. 200 | We then check differents conditions : 201 | - If the mouse ray intersect with a current square : 202 | - then if this same square was previously not hovered. 203 | - and then if there is the left click button pressed from the mouse. 204 | - Else if the mouse ray leave a square previously hovered. 205 | */ 206 | 207 | // Loop which give successively to array the values of the SQUARES array. 208 | for(array of SQUARES){ 209 | 210 | // Check if ray intersect with a current square (index 0 of array) 211 | if(ray.intersectActor(array[0], false).length > 0){ 212 | 213 | if(array[1] == "unHover"){ 214 | // if true, set the square new situation to isHover 215 | array[1] = "isHover"; 216 | // and call the local mouse method with the action isHover and the related square actor 217 | this.mouse("isHover", array[0]); 218 | } 219 | 220 | // Check if the left click button of the mouse is pressed on a free square 221 | if(Sup.Input.wasMouseButtonJustPressed(0) && array[1] == "isHover"){ 222 | // if true, set the square new situation to cross 223 | array[1] = "cross"; 224 | // and call the local mouse method with the action click and the related square actor 225 | this.mouse("click", array[0]); 226 | } 227 | } 228 | 229 | // Else if ray does not intersect with a previous hovered square, the square change situation 230 | else if(array[1] == "isHover"){ 231 | // if true, set the square new situation to unHover 232 | array[1] = "unHover"; 233 | // and call the local mouse method with the action unHover and the related square actor 234 | this.mouse("unHover", array[0]); 235 | } 236 | } 237 | } 238 | [...] 239 | ``` 240 | 241 | We can now test the behavior of our mouse, we can hover, unhover and click squares, 242 | the console log return us the mouse action. 243 | 244 | We won't play our game from the console though. Now than we have tested and seen 245 | than everything work like expected we can remove the Sup.log() functions and enhance 246 | the graphical experience of our game. 247 | 248 | In our mouse method we can access to the spriteRenderer component of the square 249 | on which we are focusing and passing an action. We use the setAnimation method 250 | to change the sprite accordingly. 251 | 252 | Here the mouse method updated : 253 | 254 | ```TypeScript 255 | [...] 256 | mouse(action, square){ 257 | // change the square sprite depending of action 258 | if(action == "isHover"){ 259 | square.spriteRenderer.setAnimation("isHover"); 260 | } 261 | else if(action == "unHover"){ 262 | square.spriteRenderer.setAnimation("unHover"); 263 | } 264 | else if(action == "click"){ 265 | square.spriteRenderer.setAnimation("cross"); 266 | } 267 | } 268 | [...] 269 | ``` 270 | 271 | We can now launch the game and fill the complete board of cross. No problem to win this game. 272 | 273 | ### A turn based game 274 | 275 | The tic tac toe wouldn't be the tic tac toe with only cross, we need also the 276 | opponent to come in game, the circle. We need to add an other player by calling 277 | the computer turn after each player turn. 278 | 279 | An important value during the game to know is the one telling which **turn** is it, 280 | the player or the computer. It is a kind of flag indicator than we want to keep 281 | track of in a turn variable which will take alternatively the ''cross'' (player) 282 | and the ''circle'' value'' (computer). 283 | 284 | We initialize the variable turn by writing in the beginning of our Global script : 285 | 286 | ```TypeScript 287 | // Initialize globally the turn variable 288 | var turn : string; 289 | [...] 290 | ``` 291 | 292 | We implement the turn based game by changing the value of the turn variable after each turn. 293 | When we start the game and call for the first time the playerBehavior function (awake) 294 | we need to say that the player or the computer begin. We need to have a random function 295 | to choose between one or the other, but for now we simply give to the turn variable the string value cross. 296 | 297 | Here we add in the awake method of the Player script : 298 | 299 | ```TypeScript 300 | [...] 301 | awake() { 302 | […] 303 | turn = "cross"; // temporary start value 304 | } 305 | [...] 306 | ``` 307 | 308 | *Note : Later we will remove this line of code and change it by a function that 309 | choose randomly who from the player or the computer, start the game.* 310 | 311 | Now we want to be able to give permission to the player to play if he have the 312 | right or block him if it is the computer turn. We do this by adding a condition 313 | in the condition that check when the player click with the mouse inside the update 314 | loop. We simply need to check if it is the player turn before to call the mouse method. 315 | 316 | If it is the player turn when there is a mouse click, we then call the mouse method 317 | and also we initialize a new game turn. We create a new method **gameTurn** in the 318 | player behavior class, it will be this method that will change directly the turn 319 | string value to circle and call for the turn of computer to play. For now we will 320 | just display in the console that it is the turn of Computer, but later we will 321 | call the main computer function from here. 322 | 323 | ```TypeScript 324 | [...] 325 | gameTurn(){ 326 | // change to computer turn 327 | turn = "circle"; 328 | // Call for the computer turn 329 | Sup.log("The computer play now !") // temporary log; 330 | // change to player turn 331 | turn = "cross"; 332 | } 333 | [...] 334 | ``` 335 | 336 | We can now call our gameTurn() method in our checking condition when we click the mouse like so : 337 | 338 | ```TypeScript 339 | [...] 340 | // Check if the left click button of the mouse is pressed on a free square 341 | if(Sup.Input.wasMouseButtonJustPressed(0) && array[1] == "isHover"){ 342 | // Check if it is the player turn 343 | if(turn == "cross"){ 344 | [...] 345 | // call a game turn 346 | this.gameTurn() ; 347 | } 348 | } 349 | [...] 350 | ``` 351 | 352 | The player turn using the mouse is now complete, the player can now only choose 353 | one square to play and the console then tell us that it is now for the computer to play. 354 | 355 | All right ! For the sake of this game, it is time to give life to our virtual opponent. 356 | -------------------------------------------------------------------------------- /2SuperOXO/ch4.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Chapter 4 : Writing the Computer response, Game logic part 2* 3 | 4 | ### Algorithm of a simple computer AI 5 | 6 | What do we want from the computer and what does it mean in our case to create an 7 | artificial intelligence ? Don't worry, it is not as difficult than it appear, in 8 | fact we call here AI the fact that our program will check the behavior of the player 9 | and the global situation before to take any action instead of just automatically 10 | giving the same response to any situation. 11 | 12 | To be more precise, each time the computer will have a turn, it will check the board 13 | and the situation of each square and act accordingly to assure it's own victory. 14 | 15 | The script consist of a little algorithm. It is differents conditions like step 16 | than the computer will follow and check until it find the most appropriate response. 17 | Here what the algorithm looks like in english : 18 | 19 | By checking the game board, the computer should be able to know : 20 | 21 | * If there is a way to win the game this turn. 22 | * And if yes, just play to win. 23 | * Else if there is no way to win, but the player is going to win the next turn. 24 | * If yes, then play to block the player. 25 | * Else if there is no way to win or no danger from the player then let's play. 26 | * If there is still a free square on the board 27 | * If yes, let's play the center square, I like it the best :) 28 | * If there is no center free, let's play one of the corner square, That sound nice :) 29 | * If there is no corner free either, let's play a side square, I take what left :( 30 | * If there is no free square on the board, I guess the game is finished 31 | 32 | 33 | ### Scripting the computer behavior 34 | 35 | There is one important data to give to the computer in a way to help the checking 36 | process is what it mean to be victorious. We have to give to the computer what to 37 | achieve in the game to use it like a reference. For this, we need to write a constant 38 | that will contain all the victory lines in a Tic Tac Toe game. 39 | 40 | Simply put, here the **VICTORIES** constant than we add in our global script. 41 | 42 | ```TypeScript 43 | // All the victory lines position index that will be used for checking 44 | const VICTORIES = [ 45 | // Rows 46 | [0, 1, 2], 47 | [3, 4, 5], 48 | [6, 7, 8], 49 | // Columns 50 | [0, 3, 6], 51 | [1, 4, 7], 52 | [2, 5, 8], 53 | // Diagonals 54 | [0, 4, 8], 55 | [2, 4, 6], 56 | ]; 57 | […] 58 | ``` 59 | 60 | *Note : It is an Array of arrays, each array is a victory line and give the three 61 | index position of each square to get to be victorious.* 62 | 63 | Now we can start to write in code our algorithm. What we want first is a function 64 | that check the board and return datas that could be used to take a decision. 65 | 66 | We add a new function **checkBoard** in the Game namespace of the Global script. 67 | 68 | ```TypeScript 69 | […] 70 | // function checking the board SQUARES and return [Action, Array] 71 | function checkBoard(){ 72 | let crossCount : number = 0; 73 | let circleCount : number = 0; 74 | let freeSquare; 75 | let line; let index; 76 | let win; 77 | let block; 78 | 79 | // loop through all the line in VICTORIES 80 | for(line of VICTORIES){ 81 | // loop through all the index position in line 82 | for(index of line){ 83 | /* 84 | Check the situation of square from index 85 | - if there is a cross, increment crossCount 86 | - if there is a circle, increment circleCount 87 | - else we keep track of it as a free square 88 | */ 89 | if(SQUARES[index][1] == "cross"){ 90 | crossCount++ 91 | } 92 | else if(SQUARES[index][1] == "circle"){ 93 | circleCount++ 94 | } 95 | else{ 96 | freeSquare = SQUARES[index]; 97 | } 98 | } 99 | // Check is there if a winning line for computer 100 | if(circleCount == 2 && crossCount == 0){ 101 | win = ["Win", freeSquare]; 102 | } 103 | // Check is there is a winning line for player 104 | if(crossCount == 2 && circleCount == 0){ 105 | block = ["Block", freeSquare]; 106 | } 107 | // Reset counts for new line check 108 | crossCount = 0; 109 | circleCount = 0; 110 | } 111 | 112 | // Return datas by order of priority 113 | if(win){ 114 | return win; 115 | } 116 | else if(block){ 117 | return block; 118 | } 119 | else{ 120 | return ["Play", undefined]; 121 | } 122 | } 123 | […] 124 | ``` 125 | 126 | 127 | We then write a second function **computerTurn** (exported to access it from outside 128 | the namespace) which is the steps conditions that will return a response from the computer. 129 | This function will call first the checkBoard as a support to get informations needed before 130 | to check what is the response to give back to the player accordingly to the datas. 131 | 132 | Here the basic structure of the function : 133 | 134 | ```TypeScript 135 | […] 136 | export function computerTurn(){ 137 | 138 | // Call the function checkBoard which return [Action, Array] 139 | let check = checkBoard(); 140 | 141 | /* 142 | The computer follow the conditions as follow : 143 | - if the situation is Win, then play the freeSquare 144 | - else if the situation is Block, then play the freeSquare 145 | - else if the situation is Play, then we enter to a new branch of conditions : 146 | - if the center is free, then take it 147 | - else if one corner is free, then take it 148 | - else if one one side is free, then take it 149 | - else, the game is finished 150 | */ 151 | 152 | if(check[0] == "Win"){ 153 | Sup.log("I play to win."); 154 | } 155 | 156 | else if(check[0] == "Block"){ 157 | Sup.log("I play to block.") 158 | } 159 | 160 | else if(check[0] == "Play"){ 161 | 162 | if(false){ 163 | Sup.log("I play the center."); 164 | } 165 | 166 | else if(false){ 167 | Sup.log("I play a corner."); 168 | } 169 | 170 | else if(true){ 171 | Sup.log("I play a side."); 172 | } 173 | 174 | else{ 175 | Sup.log("The game is finished.") 176 | } 177 | } 178 | } 179 | […] 180 | ``` 181 | 182 | To be able to check if the computerTurn and checkBoard functions work fine we need 183 | to make some change inside the Player script when the player play by clicking on a 184 | square and initialize the computer turn. We replace the Sup.log in the gameTurn() 185 | method by a call for the function **Game.computerTurn()**. 186 | 187 | Here the method once we changed it : 188 | 189 | ```TypeScript 190 | […] 191 | gameTurn(){ 192 | // change to computer turn 193 | turn = "circle"; 194 | // Call for the computer turn 195 | Game.computerTurn(); 196 | // change to player turn 197 | turn = "cross"; 198 | } 199 | […] 200 | ``` 201 | 202 | We have now the complete structure of the computer turn minus the part when the 203 | situation is Play for the computer and there is no change in the SQUARES board, 204 | which make the game incomplete and illogical. 205 | 206 | Right now, it is already possible to test the computer turn in the console log 207 | but not completely, because the computer only say 'I play to block' if the player 208 | is about to win or say 'I play a side' because for demonstration we set it up this 209 | condition true by default. 210 | 211 | We need to write more to complete the computer turn condition checking, let's 212 | focus now on the part where the game situation is 'Play'. 213 | 214 | We have three conditions, let see them one by one. 215 | 216 | 1. if the center is free we take it (we know than the index of the center is 4) 217 | 218 | ```TypeScript 219 | […] 220 | else if(check[0] == "Play"){ 221 | 222 | if(SQUARES[4][1] !== "cross" && SQUARES[4][1] !== "circle"){ 223 | Sup.log("I play the center."); 224 | } 225 | […] 226 | ``` 227 | 228 | 2. else if there is free corner (we know than corners are index 0, 2, 6 and 8) 229 | 230 | ```TypeScript 231 | […] 232 | else if( 233 | SQUARES[0][1] !== "cross" && SQUARES[0][1] !== "circle" || 234 | SQUARES[2][1] !== "cross" && SQUARES[2][1] !== "circle" || 235 | SQUARES[6][1] !== "cross" && SQUARES[6][1] !== "circle" || 236 | SQUARES[8][1] !== "cross" && SQUARES[8][1] !== "circle" 237 | ){ 238 | Sup.log("I play a corner."); 239 | } 240 | […] 241 | ``` 242 | 243 | 3. else if there is free side (we know than sides are index 1, 3, 5 and 7) 244 | 245 | ```TypeScript 246 | […] 247 | else if( 248 | SQUARES[1][1] !== "cross" && SQUARES[1][1] !== "circle" || 249 | SQUARES[3][1] !== "cross" && SQUARES[3][1] !== "circle" || 250 | SQUARES[5][1] !== "cross" && SQUARES[5][1] !== "circle" || 251 | SQUARES[7][1] !== "cross" && SQUARES[7][1] !== "circle" 252 | ){ 253 | Sup.log("I play a side."); 254 | } 255 | […] 256 | ``` 257 | 258 | We have now all our conditions ready. We can now finish the function computerTurn 259 | by returning the correct square and apply the change on it. 260 | 261 | We first need to define a new function getSquare in the namespace Game, we will 262 | use it to get a free square from an array of index when we want specifically check 263 | for a free corner or a free side square. 264 | 265 | ```TypeScript 266 | […] 267 | // function that return a square that is free to play 268 | function getSquare(array){ 269 | let index; 270 | let freeSquares = new Array; 271 | 272 | /* 273 | Loop that check the array index in SQUARES 274 | and if the square is free to take, add it to the array freeSquares 275 | */ 276 | 277 | for(index of array){ 278 | if(SQUARES[index][1] !== "cross" && SQUARES[index][1] !== "circle"){ 279 | freeSquares.push(SQUARES[index]); 280 | } 281 | } 282 | // then take randomly one the square from freeSquares and return it 283 | let randomIndex = Math.floor(Math.random() * freeSquares.length); 284 | return freeSquares[randomIndex]; 285 | } 286 | […] 287 | ``` 288 | 289 | We also define a new function **playSquare** which take a square as a parameter. It's purpose 290 | is simply to modify the situation and the sprite rendering when the computer call for it. 291 | 292 | ```TypeScript 293 | […] 294 | function playSquare(square){ 295 | // apply change on the actor and change the situation to circle 296 | square[0].spriteRenderer.setAnimation("circle"); 297 | square[1] = "circle"; 298 | } 299 | […] 300 | ``` 301 | 302 | We can now call the function playSquare with the related square each time our condition 303 | is true in the computerTurn function. 304 | 305 | Here the (nearly) completed computerTurn function : 306 | 307 | *Note : we replaced all the Sup.log by the function calls* 308 | 309 | ```TypeScript 310 | […] 311 | export function computerTurn(){ 312 | 313 | // Call the function checkBoard which return an array [Situation, freeSquare] 314 | let check = checkBoard(); 315 | 316 | /* 317 | The computer follow the conditions as follow : 318 | - if the situation is Win, then play the freeSquare 319 | - else if the situation is Block, then play the freeSquare 320 | - else if the situation is Play, then we enter to a new branch of conditions : 321 | - if the center is free, then take it 322 | - else if one corner is free, then take it 323 | - else if one one side is free, then take it 324 | - else, the game is finished 325 | */ 326 | 327 | if(check[0] == "Win"){ 328 | playSquare(check[1]); 329 | } 330 | 331 | else if(check[0] == "Block"){ 332 | playSquare(check[1]); 333 | } 334 | 335 | else if(check[0] == "Play"){ 336 | 337 | if(SQUARES[4][1] !== "cross" && SQUARES[4][1] !== "circle"){ 338 | playSquare(SQUARES[4]); 339 | } 340 | 341 | else if( 342 | SQUARES[0][1] !== "cross" && SQUARES[0][1] !== "circle" || 343 | SQUARES[2][1] !== "cross" && SQUARES[2][1] !== "circle" || 344 | SQUARES[6][1] !== "cross" && SQUARES[6][1] !== "circle" || 345 | SQUARES[8][1] !== "cross" && SQUARES[8][1] !== "circle" 346 | ){ 347 | playSquare(getSquare([0, 2, 6, 8])); 348 | } 349 | 350 | else if( 351 | SQUARES[1][1] !== "cross" && SQUARES[1][1] !== "circle" || 352 | SQUARES[3][1] !== "cross" && SQUARES[3][1] !== "circle" || 353 | SQUARES[5][1] !== "cross" && SQUARES[5][1] !== "circle" || 354 | SQUARES[7][1] !== "cross" && SQUARES[7][1] !== "circle" 355 | ){ 356 | playSquare(getSquare([1, 3, 5, 7])); 357 | } 358 | 359 | else{ 360 | Sup.log("The game is finished.") 361 | } 362 | } 363 | // end of computer turn, change to player turn 364 | turn = "cross"; 365 | } 366 | […] 367 | ``` 368 | 369 | The computer now play against the player, it use the checkBoard to understand the 370 | situation and respond accordingly. The AI is now complete. 371 | 372 | The game right now have still one issue, it is never ending, it just continue when 373 | there is a winner or the board is filled. We need now to write a function that will check the end of game. 374 | 375 | 376 | ## Scripting the victory checking 377 | 378 | At this point, the game work, we can play with the mouse and the computer can respond. 379 | We want now than the game detect and tell us if the player or the computer won the game and stop the game turns. 380 | 381 | We now write the **checkVictory()** function (exported) as follow (see comments for explanation) : 382 | 383 | ```TypeScript 384 | […] 385 | export function checkVictory(){ 386 | //set the variables 387 | let countCross: number = 0; 388 | let countCircle: number = 0; 389 | let countFreeSquares: number = 0; 390 | let line: number[]; 391 | let index: number; 392 | 393 | /* 394 | We loop through the victory lines to check this conditions for each square: 395 | - if the square is hovered by a cross, increment countCross 396 | - else if the square is hovered by a circle, increment countCircle 397 | - else, count it a a free square in the countFreeSquares variable 398 | 399 | For each line checked, we then look for this conditions : 400 | - if there is 3 cross counted, then player won 401 | - if there is 3 circle counted, then computer won 402 | 403 | At the end of the loop, if countFreeSquares is still 0 and no victory is announced, 404 | then it is a tie. 405 | */ 406 | 407 | //loop 408 | for(line of VICTORIES){ 409 | for(index of line){ 410 | if(SQUARES[index][1] == "cross"){ 411 | countCross++ 412 | } 413 | else if(SQUARES[index][1] == "circle"){ 414 | countCircle++ 415 | } 416 | else{ 417 | countFreeSquares++ 418 | } 419 | } 420 | if(countCross == 3){ 421 | Sup.log("Player won !"); 422 | turn = "end"; 423 | } 424 | if(countCircle == 3){ 425 | Sup.log("Computer won !"); 426 | turn = "end"; 427 | } 428 | // reset count to 0 for new line to check 429 | countCross = 0; 430 | countCircle = 0; 431 | } 432 | // end of loop 433 | if(countFreeSquares == 0){ 434 | Sup.log("It is a tie !"); 435 | turn = "end"; 436 | } 437 | } 438 | […] 439 | ``` 440 | 441 | We need to change one more time the gameTurn method in the Player Script to call 442 | our function checkVictory to each turn and stop or continue the turns according to the results of the function. 443 | 444 | ```TypeScript 445 | […] 446 | gameTurn(){ 447 | // check if player won 448 | Game.checkVictory(); 449 | 450 | // change to computer turn if game not ended 451 | if(turn !== "end"){ 452 | turn = "circle"; 453 | // Call for the computer turn 454 | Game.computerTurn(); 455 | // check if computer won 456 | Game.checkVictory(); 457 | 458 | // change to player turn if game not ended 459 | if(turn !== "end"){ 460 | turn = "cross"; 461 | } 462 | } 463 | } 464 | […] 465 | ``` 466 | 467 | The game is finished, we can play the full game, the player can choose where to play, 468 | the computer can respond and one can win the game or have together a tie. 469 | 470 | Sometimes, it is good to polish the overall game feeling by adding some enhancing features. 471 | It is what we will do in the last chapter. 472 | -------------------------------------------------------------------------------- /2SuperOXO/ch5.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Chapter 5, Polishing the game* 3 | 4 | ### Create a victory screen 5 | 6 | One first thing we would like to add in the game is the possibility to restart the 7 | game after we finish a first round. We can do that by simply allowing to reset to 8 | default the SQUARES board when the player do an action after the game is ended. 9 | 10 | We are going to mix this feature with the addition of an end screen which announce 11 | the victory or tie of the game. When the player click once more on this screen, 12 | the game then restart. 13 | 14 | We write first a new function **displayScreen** that will create and set our Screen Actor. 15 | 16 | ```TypeScript 17 | [...] 18 | function displayScreen(){ 19 | // Create a new Screen actor 20 | let Screen = new Sup.Actor("Screen"); 21 | // Create a new SpriteRenderer and attach it to the Screen Actor 22 | new Sup.SpriteRenderer(Screen, "Sprites/Screens"); 23 | // Set the frame of the sprite to the current turn, cross, circle or tie 24 | Screen.spriteRenderer.setAnimation(turn); 25 | // Set the frame position to the center (0, 0) and 4 on z axis to fit in the camera view 26 | Screen.setPosition(0, 0, 4); 27 | // stop the game turns 28 | turn = "end"; 29 | } 30 | [...] 31 | ``` 32 | 33 | We add the call of the victory screen by replacing the Sup.log and the repetitions 34 | of turn = "end" from the checkVictory conditions. 35 | 36 | ```TypeScript 37 | [...] 38 | export function checkVictory(){ 39 | [...] 40 | if(countCross == 3){ 41 | displayScreen(); 42 | } 43 | if(countCircle == 3){ 44 | displayScreen(); 45 | } 46 | 47 | [...] 48 | if(countFreeSquares == 0){ 49 | turn = "tie"; // we give the value 'tie' to the turn variable for the function displayScreen() 50 | displayScreen(); 51 | } 52 | } 53 | [...] 54 | ``` 55 | 56 | We have now the screens displaying, but if we want to restart and reset the game 57 | we now need to write a new behavior for the player. 58 | 59 | ### Restart the game 60 | 61 | In the Player script we create the new class **ScreenBehavior** and we say simply than 62 | we want to survey in a loop if the player will use the space key from the keyboard. 63 | To do that, we use the update method of a class. Here our new class: 64 | 65 | ```TypeScript 66 | class ScreenBehavior extends Sup.Behavior { 67 | 68 | /* 69 | When space key pressed : 70 | - Destroy the victory screen 71 | - Start a new game 72 | */ 73 | 74 | update() { 75 | if(Sup.Input.wasKeyJustPressed("SPACE")){ 76 | Sup.getActor("Screen").destroy(); 77 | Game.startGame(); 78 | } 79 | } 80 | } 81 | Sup.registerBehavior(ScreenBehavior); 82 | ``` 83 | 84 | In the global script namespace we create a function **startGame()** that we export. 85 | It will be the function that will be called when the player use SPACE key while on a victory screen. 86 | 87 | ```TypeScript 88 | [...] 89 | export function startGame(){ 90 | let square; 91 | 92 | // loop through all the square of the game and set them to default 93 | for(square of SQUARES){ 94 | square[0].spriteRenderer.setAnimation("unHover"); 95 | square[1] = "unHover"; 96 | } 97 | 98 | turn = "cross"; // give back player control 99 | } 100 | [...] 101 | ``` 102 | 103 | Finally, nothing will change if we don't attach the ScreenBehavior class to our 104 | victory screen. We do that by adding with the Screen.addBehavior() method in the 105 | displayScreen function of the Global script. 106 | 107 | ```TypeScript 108 | [...] 109 | function displayScreen(){ 110 | […] 111 | // Attach the behavior ScreenBehavior the the screen Actor 112 | Screen.addBehavior(ScreenBehavior); 113 | } 114 | [...] 115 | ``` 116 | 117 | We can now restart our game from the end screen and play as many times we wish to. 118 | 119 | ## Randomize the game starting 120 | 121 | One feature we can add to give more variety to the game is to randomize the start 122 | of the game, if it is the player or the computer who can choose the first square. 123 | We already had to use the random method in our game, we can use it again 124 | 125 | We create a new function **randomStart()** in Game namespace of global script, it will 126 | decide if yes or no the computer start the game by playing randomly one square. 127 | 128 | ```TypeScript 129 | […] 130 | function randomStart(){ 131 | //Call a random number between 0 and 1 to see if we start with computer 132 | if(Math.floor(Math.random() * 2)){ 133 | // Take a random index between the 9 squares and play this square 134 | let randomIndex = Math.floor(Math.random() * 9); 135 | playSquare(SQUARES[randomIndex]); 136 | } 137 | // then give back control to player 138 | turn = "cross"; 139 | } 140 | [...] 141 | ``` 142 | 143 | And we call now this function from our startGame function to replace the turn = 'cross', 144 | this will allow to randomize a new game each time we call the gameStart function. 145 | 146 | ```TypeScript 147 | […] 148 | export function startGame(){ 149 | let square; 150 | 151 | // loop through all the square of the game and set them to default 152 | for(square of SQUARES){ 153 | square[0].spriteRenderer.setAnimation("unHover"); 154 | square[1] = "unHover"; 155 | } 156 | 157 | randomStart() ; 158 | } 159 | [...] 160 | ``` 161 | 162 | We can also replace in the awake method of playerBehavior the turn = 'cross' by 163 | the gameStart function, like this, the randomize will work from the moment we 164 | launch the game for the first time. 165 | 166 | ```TypeScript 167 | class PlayerBehavior extends Sup.Behavior { 168 | awake() { 169 | // Call the function setSquares which build the SQUARES Array 170 | Game.setSquares(); 171 | // Call the function startGame to randomize the new game 172 | Game.startGame(); 173 | } 174 | [...] 175 | ``` 176 | 177 | ### Slowing down the game 178 | 179 | The game go really fast, the computer play directly after the player and at the end, 180 | the victory screen appear without the possibility to see the final position of the 181 | game squares. We want to slow down all this. To give time for the player to see 182 | what is happening on the board and why the game is going to end soon. 183 | 184 | We need to add a speed to the game. To achieve that we will use a javascript method 185 | that have been exported to Superpowers API : Sup.setTimeout() 186 | 187 | We will change the code to different place, to slow down the gameTurn, we can change 188 | the simple call this.gameTurn() from our update method from the playerBehavior class 189 | in the Player script : 190 | 191 | ```TypeScript 192 | […] 193 | if(Sup.Input.wasMouseButtonJustPressed(0) && array[1] == "isHover"){ 194 | // Check if it is the player turn 195 | if(turn == "cross"){ 196 | // if true, set the square new situation to cross 197 | array[1] = "cross"; 198 | // and call the local mouse method with the action click and the related square actor 199 | this.mouse("click", array[0]); 200 | // call a game turn 201 | turn = "break"; // take control away from player 202 | Sup.setTimeout(600, this.gameTurn); 203 | } 204 | } 205 | [...] 206 | ``` 207 | 208 | *Note : We change the turn variable before the timeout break because the player 209 | would then be able to click on the other squares before the computer got the chance to play.* 210 | 211 | Like we changed the turn variable before, we need to give the cross value back to 212 | the turn variable after the break but before the checkVictory function in the 213 | ameTurn Method from the playerBehavior class. 214 | 215 | 216 | ```TypeScript 217 | [...] 218 | gameTurn(){ 219 | turn = "cross"; 220 | // check if player won 221 | Game.checkVictory(); 222 | […] 223 | ``` 224 | 225 | That is slowing down our game a little bit before each computer turn. 226 | 227 | For the last move, to have time before the end screen appear, we need to play a 228 | trick and simply change the screen position from behind the background to front 229 | after the timer is out. We need to add a little function inside our function to 230 | be able to call with the setTimeout method. We change our displayScreen function 231 | from the global script like this : 232 | 233 | ```TypeScript 234 | […] 235 | function displayScreen(){ 236 | // Create a new Screen actor 237 | let Screen = new Sup.Actor("Screen"); 238 | // Create a new SpriteRenderer and attach it to the Screen Actor 239 | new Sup.SpriteRenderer(Screen, "Sprites/Screens"); 240 | // Set the frame of the sprite to the current turn, cross, circle or tie 241 | Screen.spriteRenderer.setAnimation(turn); 242 | // Attach the behavior ScreenBehavior the the screen Actor 243 | Screen.addBehavior(ScreenBehavior); 244 | 245 | // Set the frame position to the center (0, 0) and -2 on z axis to be behind the board backgroung 246 | Screen.setPosition(0, 0, -2); 247 | turn = "end"; 248 | 249 | function displayFrame(){ 250 | // Set the frame position to the center (0, 0) and 4 on z axis to be in front of the board backgroung 251 | Screen.setPosition(0, 0, 4); 252 | } 253 | // load the displayFrame function after 2000 millisecondes 254 | Sup.setTimeout(2000, displayFrame); 255 | } 256 | […] 257 | ``` 258 | 259 | Yes ! this time our game is ready for release. :) 260 | -------------------------------------------------------------------------------- /2SuperOXO/ch6.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #2 SUPER OXO 2 | ## *Chapter 6, Complete Game Source Reference* 3 | 4 | ### Assets files 5 | 6 | * board.png 7 | * symbols.png 8 | * screens.png 9 | * scripts 10 | * global.ts 11 | * player.ts 12 | 13 | ### Project structure 14 | 15 | * Game (Startup Scene) 16 | * Sprites 17 | * Symbols 18 | * Screens 19 | * Board 20 | * Scripts 21 | * Global 22 | * Player 23 | 24 | ### Scripts sources 25 | 26 | * global.ts 27 | 28 | ```TypeScript 29 | // Initialize globally the SQUARES Array 30 | const SQUARES = new Array; 31 | 32 | // Initialize globally the ray casting 33 | var ray = new Sup.Math.Ray(); 34 | 35 | // Initialize globally the turn variable 36 | var turn : string; 37 | 38 | // All the victory lines position index that will be used for checking 39 | const VICTORIES = [ 40 | // Rows 41 | [0, 1, 2], 42 | [3, 4, 5], 43 | [6, 7, 8], 44 | // Columns 45 | [0, 3, 6], 46 | [1, 4, 7], 47 | [2, 5, 8], 48 | // Diagonals 49 | [0, 4, 8], 50 | [2, 4, 6], 51 | ]; 52 | 53 | namespace Game{ 54 | 55 | // define and export the function setSquare 56 | export function setSquares(){ 57 | 58 | /* 59 | Build the SQUARES array with arrays 60 | 0 : the Square Actor of current index 61 | 1 : default string value of situation : "unHover" 62 | */ 63 | 64 | // define a local addSquare function 65 | function addSquare(index){ 66 | // get the name of current square 67 | let name = "Square" + index.toString(); 68 | 69 | // get the actor from the Game scene 70 | let square = Sup.getActor("Board").getChild(name); 71 | 72 | // push the square array in SQUARES array to the next index 73 | SQUARES.push([square, "unHover"]); 74 | } 75 | 76 | // loop calling 9 times the function addSquare with the square index as parameter 77 | for(let i = 0; i < 9; i++){ 78 | addSquare(i); 79 | } 80 | } 81 | 82 | // function checking the board SQUARES and return [Action, Array] 83 | function checkBoard(){ 84 | let crossCount : number = 0; 85 | let circleCount : number = 0; 86 | let freeSquare; 87 | let line; let index; 88 | let win; 89 | let block; 90 | 91 | // loop through all the line in VICTORIES 92 | for(line of VICTORIES){ 93 | // loop through all the index position in line 94 | for(index of line){ 95 | /* 96 | Check the situation of square from index 97 | - if there is a cross, increment crossCount 98 | - if there is a circle, increment circleCount 99 | - else we keep track of it as a free square 100 | */ 101 | if(SQUARES[index][1] == "cross"){ 102 | crossCount++ 103 | } 104 | else if(SQUARES[index][1] == "circle"){ 105 | circleCount++ 106 | } 107 | else{ 108 | freeSquare = SQUARES[index]; 109 | } 110 | } 111 | // Check is there if a winning line for computer 112 | if(circleCount == 2 && crossCount == 0){ 113 | win = ["Win", freeSquare]; 114 | } 115 | // Check is there is a winning line for player 116 | if(crossCount == 2 && circleCount == 0){ 117 | block = ["Block", freeSquare]; 118 | } 119 | // Reset counts for new line check 120 | crossCount = 0; 121 | circleCount = 0; 122 | } 123 | 124 | // Return datas by order of priority 125 | if(win){ 126 | return win; 127 | } 128 | else if(block){ 129 | return block; 130 | } 131 | else{ 132 | return ["Play", undefined]; 133 | } 134 | } 135 | 136 | export function computerTurn(){ 137 | 138 | // Call the function checkBoard which return [Action, Array] 139 | let check = checkBoard(); 140 | 141 | /* 142 | The computer follow the conditions as follow : 143 | - if the situation is Win, then play the freeSquare 144 | - else if the situation is Block, then play the freeSquare 145 | - else if the situation is Play, then we enter to a new branch of conditions : 146 | - if the center is free, then take it 147 | - else if one corner is free, then take it 148 | - else if one one side is free, then take it 149 | - else, the game is finished 150 | */ 151 | 152 | if(check[0] == "Win"){ 153 | playSquare(check[1]); 154 | } 155 | 156 | else if(check[0] == "Block"){ 157 | playSquare(check[1]); 158 | } 159 | 160 | else if(check[0] == "Play"){ 161 | 162 | if(SQUARES[4][1] !== "cross" && SQUARES[4][1] !== "circle"){ 163 | playSquare(SQUARES[4]); 164 | } 165 | 166 | else if( 167 | SQUARES[0][1] !== "cross" && SQUARES[0][1] !== "circle" || 168 | SQUARES[2][1] !== "cross" && SQUARES[2][1] !== "circle" || 169 | SQUARES[6][1] !== "cross" && SQUARES[6][1] !== "circle" || 170 | SQUARES[8][1] !== "cross" && SQUARES[8][1] !== "circle" 171 | ){ 172 | playSquare(getSquare([0, 2, 6, 8])); 173 | } 174 | 175 | else if( 176 | SQUARES[1][1] !== "cross" && SQUARES[1][1] !== "circle" || 177 | SQUARES[3][1] !== "cross" && SQUARES[3][1] !== "circle" || 178 | SQUARES[5][1] !== "cross" && SQUARES[5][1] !== "circle" || 179 | SQUARES[7][1] !== "cross" && SQUARES[7][1] !== "circle" 180 | ){ 181 | playSquare(getSquare([1, 3, 5, 7])); 182 | } 183 | 184 | else{ 185 | Sup.log("The game is finished.") 186 | } 187 | } 188 | } 189 | 190 | // function that return a square that is free to play 191 | function getSquare(array){ 192 | let index; 193 | let freeSquares = new Array; 194 | 195 | /* 196 | Loop that check the array index in SQUARES 197 | and if the square is free to take, add it to the array freeSquares 198 | */ 199 | 200 | for(index of array){ 201 | if(SQUARES[index][1] !== "cross" && SQUARES[index][1] !== "circle"){ 202 | freeSquares.push(SQUARES[index]); 203 | } 204 | } 205 | // then take randomly one the square from freeSquares and return it 206 | let randomIndex = Math.floor(Math.random() * freeSquares.length); 207 | return freeSquares[randomIndex]; 208 | } 209 | 210 | function playSquare(square){ 211 | // apply change on the actor and change the situation to circle 212 | square[0].spriteRenderer.setAnimation("circle"); 213 | square[1] = "circle"; 214 | } 215 | 216 | export function checkVictory(){ 217 | //set the variables 218 | let countCross: number = 0; 219 | let countCircle: number = 0; 220 | let countFreeSquares: number = 0; 221 | let line: number[]; 222 | let index: number; 223 | 224 | /* 225 | We loop through the victory lines to check this conditions for each square: 226 | - if the square is hovered by a cross, increment countCross 227 | - else if the square is hovered by a circle, increment countCircle 228 | - else, count it a a free square in the countFreeSquares variable 229 | 230 | For each line checked, we then look for this conditions : 231 | - if there is 3 cross counted, then player won 232 | - if there is 3 circle counted, then computer won 233 | 234 | At the end of the loop, if countFreeSquares is still 0 and no victory is announced, 235 | then it is a tie. 236 | */ 237 | 238 | //loop 239 | for(line of VICTORIES){ 240 | for(index of line){ 241 | if(SQUARES[index][1] == "cross"){ 242 | countCross++ 243 | } 244 | else if(SQUARES[index][1] == "circle"){ 245 | countCircle++ 246 | } 247 | else{ 248 | countFreeSquares++ 249 | } 250 | } 251 | if(countCross == 3){ 252 | displayScreen(); 253 | } 254 | if(countCircle == 3){ 255 | displayScreen(); 256 | } 257 | // reset count to 0 for new line to check 258 | countCross = 0; 259 | countCircle = 0; 260 | } 261 | // end of loop 262 | if(countFreeSquares == 0){ 263 | turn = "tie"; // we give the value 'tie' to the turn variable for the function displayScreen() 264 | displayScreen(); 265 | } 266 | } 267 | 268 | function displayScreen(){ 269 | // Create a new Screen actor 270 | let Screen = new Sup.Actor("Screen"); 271 | // Create a new SpriteRenderer and attach it to the Screen Actor 272 | new Sup.SpriteRenderer(Screen, "Sprites/Screens"); 273 | // Set the frame of the sprite to the current turn, cross, circle or tie 274 | Screen.spriteRenderer.setAnimation(turn); 275 | // Attach the behavior ScreenBehavior the the screen Actor 276 | Screen.addBehavior(ScreenBehavior); 277 | 278 | // Set the frame position to the center (0, 0) and -2 on z axis to be behind the board backgroung 279 | Screen.setPosition(0, 0, -2); 280 | turn = "end"; 281 | 282 | function displayFrame(){ 283 | // Set the frame position to the center (0, 0) and 4 on z axis to be in front of the board backgroung 284 | Screen.setPosition(0, 0, 4); 285 | } 286 | // load the displayFrame function after 3000 millisecondes 287 | Sup.setTimeout(2000, displayFrame); 288 | } 289 | 290 | export function startGame(){ 291 | let square; 292 | 293 | // loop through all the square of the game and set them to default 294 | for(square of SQUARES){ 295 | square[0].spriteRenderer.setAnimation("unHover"); 296 | square[1] = "unHover"; 297 | } 298 | 299 | randomStart(); 300 | } 301 | 302 | function randomStart(){ 303 | //Call a random number between 0 and 1 to see if we start with computer 304 | if(Math.floor(Math.random() * 2)){ 305 | // Take a random index between the 9 squares and play this square 306 | let randomIndex = Math.floor(Math.random() * 9); 307 | playSquare(SQUARES[randomIndex]); 308 | } 309 | // then give back control to player 310 | turn = "cross"; 311 | } 312 | } 313 | ``` 314 | 315 | * player.ts 316 | 317 | ```TypeScript 318 | class PlayerBehavior extends Sup.Behavior { 319 | awake() { 320 | // Call the function setSquares which build the SQUARES Array 321 | Game.setSquares(); 322 | // Call the function startGame to randomize the new game 323 | Game.startGame(); 324 | } 325 | 326 | // mouse method receiving parameters of the player action and the square related to this action 327 | mouse(action, square){ 328 | // change the square sprite depending of action 329 | if(action == "isHover"){ 330 | square.spriteRenderer.setAnimation("isHover"); 331 | } 332 | else if(action == "unHover"){ 333 | square.spriteRenderer.setAnimation("unHover"); 334 | } 335 | else if(action == "click"){ 336 | square.spriteRenderer.setAnimation("cross"); 337 | } 338 | } 339 | 340 | gameTurn(){ 341 | turn = "cross"; 342 | // check if player won 343 | Game.checkVictory(); 344 | 345 | // change to computer turn if game not ended 346 | if(turn !== "end"){ 347 | turn = "circle"; 348 | // Call for the computer turn 349 | 350 | Game.computerTurn(); 351 | // check if computer won 352 | Game.checkVictory(); 353 | 354 | // change to player turn if game not ended 355 | if(turn !== "end"){ 356 | turn = "cross"; 357 | } 358 | } 359 | } 360 | 361 | update() { 362 | // Refresh the ray casting to the mouse position inside the camera screen 363 | ray.setFromCamera(Sup.getActor("Camera").camera, Sup.Input.getMousePosition()); 364 | 365 | // Create a new empty variable that will as value the differents array of the SQUARES constant 366 | let array; 367 | 368 | /* 369 | We loop through all the arrays in SQUARES and give the current array to the square variable. 370 | We then check differents conditions : 371 | - If the mouse ray intersect with a current square : 372 | - then if this same square was previously not hovered. 373 | - and then if there is the left click button pressed from the mouse. 374 | - Else if the mouse ray leave a square previously hovered. 375 | */ 376 | 377 | // Loop which give successively to array the values of the SQUARES array. 378 | for(array of SQUARES){ 379 | 380 | // Check if ray intersect with a current square (index 0 of array) 381 | if(ray.intersectActor(array[0], false).length > 0){ 382 | 383 | if(array[1] == "unHover"){ 384 | // if true, set the square new situation to isHover 385 | array[1] = "isHover"; 386 | // and call the local mouse method with the action isHover and the related square actor 387 | this.mouse("isHover", array[0]); 388 | } 389 | 390 | // Check if the left click button of the mouse is pressed on a free square 391 | if(Sup.Input.wasMouseButtonJustPressed(0) && array[1] == "isHover"){ 392 | // Check if it is the player turn 393 | if(turn == "cross"){ 394 | // if true, set the square new situation to cross 395 | array[1] = "cross"; 396 | // and call the local mouse method with the action click and the related square actor 397 | this.mouse("click", array[0]); 398 | // call a game turn 399 | turn = "break"; // take control away from player 400 | Sup.setTimeout(600, this.gameTurn); 401 | } 402 | } 403 | } 404 | 405 | // Else if ray does not intersect with a previous hovered square, the square change situation 406 | else if(array[1] == "isHover"){ 407 | // if true, set the square new situation to unHover 408 | array[1] = "unHover"; 409 | // and call the local mouse method with the action unHover and the related square actor 410 | this.mouse("unHover", array[0]); 411 | } 412 | } 413 | } 414 | } 415 | Sup.registerBehavior(PlayerBehavior); 416 | 417 | class ScreenBehavior extends Sup.Behavior { 418 | 419 | /* 420 | when space key pressed : 421 | - Destroy the victory screen 422 | - Start a new game 423 | */ 424 | 425 | update() { 426 | if(Sup.Input.wasKeyJustPressed("SPACE")){ 427 | Sup.getActor("Screen").destroy(); 428 | Game.startGame(); 429 | } 430 | } 431 | } 432 | Sup.registerBehavior(ScreenBehavior); 433 | ``` 434 | -------------------------------------------------------------------------------- /2SuperOXO/img/3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/3D.png -------------------------------------------------------------------------------- /2SuperOXO/img/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/assets.png -------------------------------------------------------------------------------- /2SuperOXO/img/behavior.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/behavior.png -------------------------------------------------------------------------------- /2SuperOXO/img/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/board.png -------------------------------------------------------------------------------- /2SuperOXO/img/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/camera.png -------------------------------------------------------------------------------- /2SuperOXO/img/screens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/screens.png -------------------------------------------------------------------------------- /2SuperOXO/img/symbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/symbols.png -------------------------------------------------------------------------------- /2SuperOXO/img/tictactoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/2SuperOXO/img/tictactoe.png -------------------------------------------------------------------------------- /3SuperSokoban/README.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | 3 | ##Learn game development with a sokoban game 4 | 5 | 6 | ### Summary : 7 | 8 | 1. [Planning the game](ch1.md#chapter-1-plan-the-game) 9 | * [Game introduction](ch1.md#introduction) 10 | * [Features for a minimum viable game](ch1.md#features-we-need-for-a-minimal-viable-game) 11 | * [Advices about learning game development](ch1.md#advices-about-the-learning-process-for-game-development) 12 | 2. [Shaping the game structure](ch2.md#chapter-2-shaping-the-game-structure) 13 | * [Settings](ch2.md#settings) 14 | * [Loading assets](ch2.md#loading-game-assets) 15 | * [Tiles and level mapping](ch2.md#tiles-and-level-mapping) 16 | * [Building the game scene](ch2.md#building-the-game-scene) 17 | 3. [World and player logic](ch3.md#chapter-world-and-player-logic) 18 | * [Setting main script](ch3.md#setting-main-script) 19 | * [Player position](ch3.md#player-position) 20 | * [Player movement and world interaction](ch3.md#player-movement-and-world-interaction) 21 | 4. [Level scripting](ch4.md#chapter-4-level-scripting) 22 | * [Level checking](ch4.md#level-checking) 23 | * [Level transitions](ch4.md#levels-transition) 24 | * [Victory scene](ch4.md#victory-scene) 25 | 5. [Polishing the game](ch5.md#chapter-5--polishing-the-game) 26 | * [Restarting a level when stuck](ch5.md#restarting-a-level) 27 | * [Adding sound and music](ch5.md#adding-sound-and-music-to-the-game) 28 | * [Adding new levels](ch5.md#add-new-levels-to-the-game) 29 | 6. [Game source reference](ch6.md#chapter-6--complete-game-source-reference) 30 | * [Assets files](ch6.md#assets-files) 31 | * [Scripts sources](ch6.md#scripts-source) 32 | 33 | 34 | ### What we will learn in this tutorial : 35 | 36 | - work with tile map and tileset in superpowers 37 | - build a complete level and load it in a scene 38 | - script a simple puzzle game 39 | - Superpowers and TypeScript API methods used: 40 | -------------------------------------------------------------------------------- /3SuperSokoban/ch1.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | ## *Chapter 1 : Plan the game* 3 | 4 | ### Introduction 5 | 6 | In this tutorial we will learn to make a [Sukoban Game][1]. 7 | 8 | ![sokoban.png](img/sokoban.png) 9 | 10 | It consist of different levels composed of tiles where each tile is either a floor or a wall. 11 | 12 | Some floor tiles are covered with boxes, and some other floor tile are marked as 13 | storage target, there is the same amount of box and storage location in one level. 14 | 15 | The player may move horizontally or vertically onto empty floor tile (but is always blocked by a wall). 16 | 17 | The player can move into a box, which pushes it into the tile beyond if the next tile 18 | beyond is not blocked by a wall or an other box. If the box cannot be pushed, the player is blocked. 19 | 20 | Pass to the next level and win the game is to solve the differents puzzles by moving 21 | each box to one storage location. 22 | 23 | 24 | 25 | ***Note about the video game history:*** *The first* [Sokoban][1] *was released in 1982 and since 26 | then have known many sequels or variants, but the core puzzle gameplay remained 27 | mostly the same until today. You can play thousands of differents puzzles patterns 28 | on this [web site][2].* 29 | 30 | 31 | ### Features we need for a minimal viable game 32 | 33 | * A moving character on a tiled map 34 | * The character blocked by wall and boxes 35 | * Pushing box on free space or target destination 36 | * Changing level when puzzle solved 37 | 38 | ### Advices about the learning process for game development 39 | 40 | If you are doing this game for the first time, I recommend you to follow step by 41 | step the process because what is important is to make it work for you, after that 42 | you can then do it a second time with the modification you wish, breaking the code 43 | and do any kind of tests you want for the purpose of understanding how the game work 44 | in details and finally, I recommend you do it a third time from scratch without 45 | looking at the tutorial only as a reference when you are blocked, facing the 46 | problems by yourself and solving them is the best way to learn and keep in long 47 | term memory the process of game making and progamming. It is how I conceive learning 48 | video game development for this tutorial series, my philosophy : **learn by doing it 49 | three times, deeper each time**. :-) 50 | 51 | [1]: https://en.wikipedia.org/wiki/Sokoban 52 | [2]: http://sokoban.info/ 53 | -------------------------------------------------------------------------------- /3SuperSokoban/ch2.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | ## *Chapter 2 : Shaping the game structure* 3 | 4 | ### Settings 5 | 6 | Like before, we will first use Superpowers to build our game as much as we can 7 | before to start the programming process. This time it is not only about loading 8 | assets and combine them in a Scene which we will use in our code, but also we will 9 | take advantage of the map editor from Superpowers to build our levels. 10 | 11 | First let create a new project and go in the settings. We will choose a standard 12 | **screen ratio** of 4 / 3 for our game. We will use tiles of 16x16 pixels, our screen 13 | size will be something in pixels like (4*16*x in width and 3*16*x in height like 14 | 640 x 480 for example). 15 | 16 | We can change the default **camera mode** to 2D, but you will be able to change anytime 17 | you want in the scene. 18 | 19 | For **Sprite** and **Tile Map** we change the default **Pixels per Unit** from 100 by default 20 | to 16, which will serve as 1 unit for 16 pixel in the camera screen. This way it 21 | will be easier later in our program to say by example to move our character to one 22 | unit, which be understood directly as to move to 16 pixels. In fact it will help 23 | us to have a syncronicity between our 16x16 tiled map and our Camera Screen. 24 | 25 | ![settings.png](img/settings.png) 26 | 27 | 28 | ### Loading game assets 29 | 30 | We can start to build our structure, we add the **assets** we will need, new scenes, 31 | sprites, tileset, tilemaps, sounds and scripts. 32 | 33 | 34 | We will proceed step by step as we build the game, however here the complete finale 35 | asset structure than we can build now (empty assets) if we want. 36 | 37 | 38 | ![assets.png](img/assets.png) 39 | 40 | * Game (Will be our Startup Scene) 41 | * Sprites 42 | * Character (sprite of the player character) 43 | * Next (transition sprite) 44 | * Reset (information sprite) 45 | * Scripts 46 | * Main (Will contain our main game functions) 47 | * Player (The player behavior) 48 | * Level (The level behavior) 49 | * Levels 50 | * Tiles (Tile Set) 51 | * LevelTemplate (Tile Map) 52 | * Level1 (Tile Map) 53 | * Level2 (Tile Map) 54 | * Level3 (Tile Map) 55 | * Sounds 56 | * Push 57 | * Blocked 58 | * EndSound 59 | * GameMusic 60 | * Victory //this folder contain the assets we will use to finish the game 61 | * Scene 62 | * Screen (Sprite) 63 | * EndMusic (Sound) 64 | 65 | 66 | We start to create 3 sprites, Character, Next and Reset and we give to each the 67 | image file (png) with the same name. All should already be 16 pixels per unit, 68 | but we still need to set them individually. 69 | 70 | * For the Reset sprite, the **grid** is the same size than the image 160 x 16. 71 | * For the Next sprite, the grid is composed of two frame of 128 x 128. 72 | * We create an animation called next and we give the start frame 0 and the last frame 1. 73 | * We also set the **Frames / second** to 2 instead of 10, that will slow down the animation. 74 | 75 | ![next.png](img/next.png) 76 | 77 | * For the character sprite, we need to set the grid to 16x16, the same size than, 78 | like we will se later, the tiles of our game world. 79 | * We set the origin to the sprite to 0, 0 (down and left) 80 | * We need to create 4 animations (that won't be much animated, because they all have one frame) : 81 | * down (frame 0, 0) 82 | * up (frame 1, 1) 83 | * right (frame 2, 2) 84 | * left (frame 3, 3) 85 | 86 | ![character.png](img/character.png) 87 | 88 | 89 | ### Tiles and level mapping 90 | 91 | We will now learn how to build levels that we will use for our game. Before to 92 | start mapping we need a tile set, which is the graphical tile that we will use to draw (map) our level. 93 | 94 | In the folder Levels, we create an asset **Tile Set** than we call Tiles. 95 | We then load the image file tiles.png and set the grid to 16x16. 96 | 97 | Note that it is important to have our last tile as an empty tile because will 98 | mapping we will use it for transparency between layer and also an eraser Tile. 99 | 100 | Each tile have it's own index and we will use them many time in our code. The first 101 | tile start to 0, and the last tile can also be called with the index -1. 102 | 103 | Still in the folder Levels, we create now a **Tile Map** that we call LevelTemplate and 104 | that will serve as the main model for all our level than we will duplicate (Ctrl+D) from this one. 105 | 106 | * We first attach the tile set Tiles to it (by drag and drop or by writing the path name Levels/Tiles) 107 | * We change the map size to 16 x 12 tiles (wich respect the screen ratio 4 x 3). 108 | * We create 2 layers, World for layer 0, and Actors for layer 1. 109 | 110 | *Note than the layer order is important as it is related to which come first in 111 | the camera view, 0 is the farthest of all layers and the other overlaps each other.* 112 | 113 | By default, the layers are covered with -1, it is also why the empty and transparent 114 | tile is important if we want to be able to draw our map on a blank canvas and have 115 | more than one layer. 116 | 117 | Here the way we will draw the map, we have the tiles on the right side, we can 118 | select each one and brush or fill the map. Let's do some tests on our World layer 119 | with the two first tiles, the wall and floor. 120 | 121 | We have a simple map, it is generic for our LevelTemplate Tile Map. 122 | 123 | ![template.png](img/template.png) 124 | 125 | 126 | First let duplicate LevelTemplate and call our new Tile Map Level1, then we can 127 | draw what we want. 128 | 129 | We also add target tile in the World layer, (tile index 2). 130 | 131 | Here the layer World for our Level 1 we will use for our game. 132 | 133 | 134 | ![level1world.png](img/level1world.png) 135 | 136 | For each of our maps we also need boxes and the player start position. We put as 137 | much boxes as there is target. The boxes and the player start position (index 3 138 | and 4) have to be on the layer Actors. 139 | 140 | Here the layer Actor for our Level1 and both layers together. 141 | 142 | ![level1actors.png](img/level1actors.png) 143 | 144 | ![level1.png](img/level1.png) 145 | 146 | And here also the complete maps for our Level2 and Level3, the puzzles are simple 147 | and for now this choice will be useful to do test on the game mechanic as we program 148 | the logic, it will be easy later to remplace them by more challenging puzzles. 149 | 150 | 151 | ![level2.png](img/level2.png) 152 | 153 | ![level3.png](img/level3.png) 154 | 155 | 156 | 157 | ### Building the game Scene 158 | 159 | 160 | We start by creating a new Actor called Level to which we attach a new component 161 | Tile Map Renderer. 162 | 163 | * We attach to this actor our Tile Map LevelTemplate (drag and drop or path). 164 | * We check the Level actor is in position 0, 0, 0. 165 | 166 | We then create a new Actor called Camera to which we attach a new component Camera 167 | and we switch the mode to Orthographic with an **Orthographic Scale** of 12. 168 | 169 | * To have the camera fitting the center of the level, we need to have a position to (8, 6, 6). 170 | 171 | 172 | We add now a new actor called Player to which we attach a new component Sprite Renderer. 173 | 174 | * We attach to this actor our Sprite Character (drag and drop or path). 175 | * We check the Player actor is in position 0, 0, 2. (2 because we want it to appear 176 | in front of our level) 177 | 178 | We set our Game scene in the settings as the **Startup Scene**. 179 | 180 | We can launch the game to check if everything display correctly. 181 | 182 | ![scene.png](img/scene.png) 183 | 184 | ![check.png](img/check.png) 185 | 186 | We add also, as childs of the Level actor, two new actors, Reset and Next, to 187 | which we attache new Component Sprite Renderer with the sprites of the same name. 188 | 189 | * Reset actor position (8, 0.5, 4) 190 | * Next actor position (8, 6, 4) 191 | 192 | For the Next actor, we set the default animation to next. 193 | 194 | We also switch off both of them the visible parameter, because we won't use them 195 | in the beginning of our development. 196 | 197 | All right, we now have our complete game structure in place. We can now start programming. 198 | -------------------------------------------------------------------------------- /3SuperSokoban/ch3.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | ## *Chapter World and player logic* 3 | 4 | We create 3 scripts asset : 5 | 6 | * Main (we can erase the template code as we won't make any class here) 7 | * Player 8 | * Level 9 | 10 | And in the Game scene we attach a new component behavior to the Player Actor and 11 | add the PlayerBehavior class. The same for the Level actor with the class LevelBehavior. 12 | 13 | ### Setting main script 14 | 15 | In the Main script we first need to set some global variables and datas. 16 | 17 | 18 | ```TypeScript 19 | // List the game levels 20 | const LEVELS = { 21 | 0:'LevelTemplate', 22 | 1:'Level1', 23 | 2:'Level2', 24 | 3:'Level3' 25 | }; 26 | 27 | // List the map layers 28 | enum Layers{ 29 | World = 0, 30 | Actors = 1 31 | }; 32 | 33 | // List the map tiles 34 | enum Tiles{ 35 | Empty = -1, 36 | Wall = 0, 37 | Floor = 1, 38 | Target = 2, 39 | Crate = 3, 40 | Start = 4, 41 | Packet = 5 42 | }; 43 | 44 | // Game level won flag 45 | var isLevelWon : boolean = false; 46 | 47 | // Current Level, Start Level 1 48 | var levelCount : number = 1; 49 | 50 | // Number of level, checked when game awake 51 | var levelMax : number; 52 | 53 | // Set new player position to map origin 54 | var playerPosition = new Sup.Math.Vector2(0, 0); 55 | ``` 56 | 57 | From here we can start a new **Game namespace** that will contain all our main functions for the game. 58 | 59 | Let start with a simple function **getMaxLevel** that check how many level the game have. 60 | And let's call it directly in our Main script. 61 | 62 | ```TypeScript 63 | [...] 64 | namespace Game{ 65 | export function getMaxLevel(){ 66 | levelMax = 0; 67 | // Add one for each level in LEVELS 68 | for(let level in LEVELS){ 69 | levelMax++; 70 | } 71 | } 72 | } 73 | 74 | // Call the getMaxLevel function when game is launched 75 | Game.getMaxLevel(); 76 | ``` 77 | 78 | *Note : if we write a function to know the number of level instead of writing directly 79 | in the variable, 3, it is because we want the program to check automaticaly if there is 80 | level which have been added or removed, it will come in handy when we want to add 81 | simply levels later to the game.* 82 | 83 | ### Player position 84 | 85 | We need a function that detect the player position when a level start, we write 86 | a function **getPosition** that check our level for the Start tile. 87 | 88 | We add in the Game namespace of the Main script : 89 | 90 | ```TypeScript 91 | [...] 92 | export function getPosition(level){ 93 | /* 94 | Scan the 16x12 level in order to : 95 | - Set the playerPosition vector from the Start tile position on Actor layer 96 | - Change the Start tile by an empty tile (the Player Sprite will come instead) 97 | */ 98 | 99 | // Set the variable to default 100 | playerPosition.x = 0, playerPosition.y = 0; 101 | 102 | for(let row = 0; row < 12; row++){ 103 | for(let column = 0; column < 16; column++){ 104 | 105 | // get the tile on Actors layer for x = column and y = row positions 106 | let actorTile = level.getTileAt(Layers.Actors, column, row); 107 | 108 | if(actorTile === Tiles.Start){ 109 | 110 | // remove the Start tile and replace with empty tile 111 | level.setTileAt(Layers.Actors, column, row, Tiles.Empty); 112 | 113 | // set position to x, y on level map 114 | playerPosition.add(column, row); 115 | } 116 | } 117 | } 118 | } 119 | [...] 120 | ``` 121 | 122 | We will know awake our behaviors, to be sure to avoid conflict in the awaking order, 123 | there is two steps in initializing a class in Superpowers, first all the awake method 124 | of all behavior start, then the start methods are called (and only then, the update loop start), 125 | we want the level to be fully loaded before to awake the Player actor. 126 | 127 | 128 | We use the method awake for the levelBehavior, where we will call our function getPosition : 129 | 130 | ```TypeScript 131 | class LevelBehavior extends Sup.Behavior { 132 | 133 | level = this.actor.tileMapRenderer; 134 | 135 | awake() { 136 | // set Level actor to the current level map path 137 | this.level.setTileMap("Levels/"+LEVELS[levelCount]); 138 | 139 | // call the getPositions function with the current tile map as parameter 140 | Game.getPosition(this.level.getTileMap()); 141 | } 142 | 143 | update() { 144 | 145 | } 146 | } 147 | Sup.registerBehavior(LevelBehavior); 148 | ``` 149 | 150 | Then we use the start method for the PlayerBehavior where we will set the start 151 | position to the player Actor: 152 | 153 | ```TypeScript 154 | class PlayerBehavior extends Sup.Behavior { 155 | 156 | start() { 157 | // set position of Player actor to the playerPosition 2D vector 158 | this.actor.setPosition(playerPosition); 159 | } 160 | 161 | update() { 162 | 163 | } 164 | } 165 | Sup.registerBehavior(PlayerBehavior); 166 | ``` 167 | 168 | We now have our game launching with the first level and our player to the starting 169 | position. We now want our player to be able to move and interact in the level. 170 | 171 | 172 | ### Player movement and world interaction 173 | 174 | We do this in two part, we first need to build a custom method than we call move 175 | which take coordinates as parameters and is executed when a movement key is pressed. 176 | This method will contain the condition we need to check for the player interaction with the world. 177 | 178 | ```TypeScript 179 | […] 180 | move(x, y){ 181 | 182 | /* 183 | We update the future position of the player and start to check condition : 184 | - (1) if the next tile is an empty floor, the player can move, canMove is true 185 | - (2) else, canMove is false 186 | - (3) if the next tile is a box, then we check a second branch of condition : 187 | - (4) if the the next tile after the box tile is an empty floor, the player can push the box 188 | - (5) else, canMove is false 189 | - (6) if canMove is true, then we update the SpriteRenderer of the Player Actor to the new position 190 | - (7) else, we take back the previous coordinates for the player position 191 | */ 192 | 193 | let canMove: boolean; 194 | 195 | // Set to new coordinates 196 | playerPosition.add(x, y); 197 | 198 | // We get the tiles index for each layer of the map from the new coordinates 199 | let level = Sup.getActor("Level").tileMapRenderer.getTileMap(); 200 | let tileWorld = level.getTileAt(Layers.World, playerPosition.x, playerPosition.y); 201 | let tileActors = level.getTileAt(Layers.Actors, playerPosition.x, playerPosition.y); 202 | 203 | // (1) 204 | if(tileWorld === Tiles.Floor || tileWorld === Tiles.Target){ 205 | canMove = true; 206 | 207 | // (3) 208 | if(tileActors == Tiles.Crate || tileActors == Tiles.Packet){ 209 | 210 | // We get the tiles index for each layer of the map from the coordinates after the new ones 211 | let nextWorldTile = level.getTileAt(Layers.World, playerPosition.x + x, playerPosition.y + y); 212 | let nextActorsTile = level.getTileAt(Layers.Actors, playerPosition.x + x, playerPosition.y + y); 213 | 214 | // (4) 215 | if(nextWorldTile == Tiles.Floor && nextActorsTile == Tiles.Empty){ 216 | 217 | level.setTileAt(Layers.Actors, playerPosition.x, playerPosition.y, Tiles.Empty); 218 | // If the next world tile is floor, the box is a crate. 219 | level.setTileAt(Layers.Actors, playerPosition.x + x, playerPosition.y + y, Tiles.Crate); 220 | 221 | } 222 | // (4) 223 | else if(nextWorldTile == Tiles.Target && nextActorsTile == Tiles.Empty){ 224 | 225 | level.setTileAt(Layers.Actors, playerPosition.x, playerPosition.y, Tiles.Empty); 226 | // If the next world tile is a target tile, the box is a packet. 227 | level.setTileAt(Layers.Actors, playerPosition.x + x, playerPosition.y + y, Tiles.Packet); 228 | 229 | } 230 | // (5) 231 | else{ 232 | canMove = false; 233 | } 234 | } 235 | } 236 | // (2) 237 | else{ 238 | canMove = false; 239 | } 240 | 241 | // (6) 242 | if(canMove === true){ 243 | // We update the Player Actor with the new playerPosition vector coordinate 244 | this.actor.setPosition(playerPosition.x, playerPosition.y); 245 | } 246 | // (7) 247 | else{ // blocked, return to previous value 248 | playerPosition.subtract(x, y); 249 | } 250 | } 251 | [...] 252 | ``` 253 | 254 | Then we can build a condition structure in the update loop of the player script 255 | to check the keyboard inputs. 256 | 257 | ```TypeScript 258 | […] 259 | update() { 260 | // if the level is NOT won, the player have control 261 | if(!isLevelWon){ 262 | if(Sup.Input.wasKeyJustPressed("UP")){ 263 | this.move(0, 1); 264 | this.actor.spriteRenderer.setAnimation('up'); 265 | } 266 | else if(Sup.Input.wasKeyJustPressed("DOWN")){ 267 | this.move(0, -1); 268 | this.actor.spriteRenderer.setAnimation('down'); 269 | } 270 | else if(Sup.Input.wasKeyJustPressed("LEFT")){ 271 | this.move(-1, 0); 272 | this.actor.spriteRenderer.setAnimation('left'); 273 | } 274 | else if(Sup.Input.wasKeyJustPressed("RIGHT")){ 275 | this.move(1, 0); 276 | this.actor.spriteRenderer.setAnimation('right'); 277 | } 278 | } 279 | } 280 | [...] 281 | ``` 282 | 283 | We have now the complete main game mechanic working, we can move the player and 284 | push the boxes until the target destinations. 285 | 286 | We now have to give a flow to our game, to check the level for the game situation 287 | and have a level transition. 288 | -------------------------------------------------------------------------------- /3SuperSokoban/ch4.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | ## Chapter 4 : Level scripting 3 | 4 | 5 | ### Level checking 6 | 7 | What we need now to complete our game is a level transition and victory checking, 8 | to do so, we need to check the level. 9 | 10 | We write a function **checkLevel** in our Main script, in the Game namespace : 11 | 12 | ```TypeScript 13 | [...] 14 | export function checkLevel(level){ 15 | 16 | /* 17 | We check all the level for the crates and targets positions. 18 | We then compare if the position of crates and targets match, if all crate are on target, the level is won. 19 | */ 20 | 21 | let boxesNumber : number = 0; 22 | let boxesPositions = []; 23 | let targetsPositions = []; 24 | 25 | for(let column = 0; column < 12; column++){ 26 | for(let row = 0; row < 16; row++){ 27 | 28 | // Take the tiles from two layers to coordinates row and column 29 | let actorTile = level.getTileAt(Layers.Actors, row, column); 30 | let worldTile = level.getTileAt(Layers.World, row, column); 31 | 32 | // If the actor tile is a box, keep the position 33 | if(actorTile === Tiles.Crate || actorTile === Tiles.Packet){ 34 | let position = new Sup.Math.Vector2(row, column); 35 | boxesPositions.push(position); 36 | 37 | // we count the total number of crate 38 | boxesNumber++; 39 | } 40 | 41 | // If the world tile is a target, keep the position 42 | if(worldTile === Tiles.Target){ 43 | let position = new Sup.Math.Vector2(row, column); 44 | targetsPositions.push(position); 45 | } 46 | } 47 | } 48 | 49 | // Check if all boxes 50 | if(checkVictory(level, boxesNumber, boxesPositions, targetsPositions)){ 51 | isLevelWon = true; 52 | levelCount++; 53 | Sup.log("DEBUG : VICTORY"); // temporary log 54 | }; 55 | } 56 | [...] 57 | ``` 58 | 59 | To see it work, we need to add a local function in the Game namespace : 60 | 61 | ```TypeScript 62 | […] 63 | function checkVictory(level, boxesNumber, boxesPositions, targetsPositions){ 64 | 65 | let posBox = new Sup.Math.Vector2; 66 | let posTarget = new Sup.Math.Vector2; 67 | let count : number = 0; 68 | 69 | for(posBox of boxesPositions){ 70 | for(posTarget of targetsPositions){ 71 | if(posBox.x === posTarget.x && posBox.y === posTarget.y){ 72 | count++; 73 | } 74 | } 75 | } 76 | 77 | if(count === boxesNumber){ 78 | return true; 79 | } 80 | } 81 | […] 82 | ``` 83 | 84 | 85 | We need to call the checkLevel function each time the player move, to do so we need to add a call in the move method of the PlayerBehavior, when the condition that check if the player can move is true. 86 | 87 | ```TypeScript 88 | […] 89 | // (6) 90 | if(canMove === true){ 91 | // We update the Player Actor with the new playerPosition vector coordinate 92 | this.actor.setPosition(playerPosition.x, playerPosition.y); 93 | 94 | Game.checkLevel(level); 95 | } 96 | […] 97 | ``` 98 | 99 | We can now play the first level and have a log when we solved the puzzle. 100 | 101 | ### Levels transition 102 | 103 | We want now a transition and be able to go to the next level up until the end of the game. 104 | 105 | First we create in the Main script a new function **setLevel** than we will call when we want to start a new level. 106 | 107 | ```TypeScript 108 | […] 109 | export function setLevel(){ 110 | //reset values to default 111 | isLevelWon = false; 112 | //reload the scene 113 | Sup.loadScene("Game"); 114 | } 115 | […] 116 | ``` 117 | 118 | Then we modify the update method of the levelBehavior. 119 | 120 | ```TypeScript 121 | […] 122 | update() { 123 | 124 | /* 125 | If the level is won we check 126 | - if it was the last level 127 | - if yes, we go to the victory screen 128 | - else we change the transition text visibility to true 129 | - if the key space is pressed 130 | - change the transition text visibility to false 131 | - change the new level tile map 132 | - call the setLevel function that will prepare the scene before to reload it 133 | */ 134 | 135 | if(isLevelWon){ 136 | if(levelCount == levelMax){ 137 | Sup.loadScene("Victory/Scene"); 138 | } 139 | else{ 140 | this.actor.getChild("Next").setVisible(true); 141 | } 142 | 143 | if(Sup.Input.wasKeyJustPressed("SPACE")){ 144 | this.actor.getChild("Next").setVisible(false); 145 | 146 | this.level.setTileMap("Levels/"+LEVELS[levelCount]); 147 | 148 | Game.setLevel(); 149 | } 150 | } 151 | } 152 | […] 153 | ``` 154 | We can now play the different level until the end, though, there will be a crash 155 | as we have not set yet the victory scene. 156 | 157 | ### Victory Scene 158 | 159 | We first create a new asset sprite Screen to which we attach the screen image. (grid size same as image size) 160 | 161 | We create a new scene Victory with a new Actor Camera which we attach a new 162 | component Camera, we set the mode to orthographic with a scale of 12. 163 | 164 | We create a new Actor Screen to which we attach a sprite renderer, with the Screen sprite asset. 165 | 166 | Both actor should be in position (0, 0) but the camera should have a z coordinate a 167 | bit in front of the image like 2. 168 | 169 | 170 | We can remove the temporary log of checkLevel and try the game. It should be complete now. 171 | 172 | The only problem is that, right now, it is impossible to do mistake without the 173 | necessity to restart the game. We will do a level reset option in the next chapter. 174 | -------------------------------------------------------------------------------- /3SuperSokoban/ch5.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | ## Chapter 5 : Polishing the game 3 | 4 | 5 | ### Restarting a level 6 | 7 | To be able to restart a level, we need to same the Tile pattern before we make 8 | change to it. We will keep the original map in an Array that we will use if the player ask to reset the level. 9 | 10 | First in the main script, we create a new global variable. 11 | 12 | 13 | ```TypeScript 14 | [...] 15 | // Original map pattern saved 16 | var mapSaved : number[][]; 17 | [...] 18 | ``` 19 | 20 | And we will use the getPosition function which check all the tile to find the 21 | start player, to also save all our tiles from the Actors layers inside the mapSaved variable. 22 | 23 | Here the updated function : 24 | 25 | ```TypeScript 26 | [...] 27 | export function getPosition(level){ 28 | /* 29 | Scan the 16x12 level in order to : 30 | - Save the tile pattern for each layer in the mapSaved array 31 | - Set the playerPosition vector from the Start tile position on Actor layer 32 | - Change the Start tile by an empty tile (the Player Sprite will come instead) 33 | */ 34 | 35 | // Set the variables to default, erase previous level 36 | mapSaved = []; 37 | playerPosition.x = 0, playerPosition.y = 0; 38 | 39 | for(let column = 0; column < 12; column++){ 40 | for(let row = 0; row < 16; row++){ 41 | 42 | // get the tile for x = row and y = column positions 43 | let actorTile = level.getTileAt(Layers.Actors, row, column); 44 | 45 | // Add the tile to the array 46 | mapSaved.push(actorTile); 47 | 48 | if(actorTile === Tiles.Start){ 49 | // remove the Start tile and replace with empty tile 50 | level.setTileAt(Layers.Actors, row, column, Tiles.Empty); 51 | 52 | // set position to x, y on level map 53 | playerPosition.add(row, column); 54 | } 55 | } 56 | } 57 | } 58 | [...] 59 | ``` 60 | 61 | Once a level start, the actors layer of the level is saved. 62 | 63 | 64 | Now, inside the Game namespace of the main Script, we add a resetLevel function. 65 | 66 | ```TypeScript 67 | [...] 68 | export function resetLevel(level){ 69 | let index : number = 0; 70 | 71 | // set all the actor tiles of the current level to the savedMap tile 72 | for(let column = 0; column < 12; column++){ 73 | for(let row = 0; row < 16; row++){ 74 | level.setTileAt(Layers.Actors, row, column, mapSaved[index]); 75 | index++ 76 | } 77 | } 78 | // call the setLevel function to prepare a new level 79 | setLevel(); 80 | } 81 | [...] 82 | ``` 83 | 84 | Now we add an Input condition in the update loop of Levelbehavior. 85 | in the level script make it invisible and visible again in the transition step. 86 | 87 | 88 | ```TypeScript 89 | [...] 90 | update() { 91 | 92 | if(isLevelWon){ 93 | if(levelCount == levelMax){ 94 | Sup.loadScene("Victory/Scene"); 95 | } 96 | else{ 97 | this.actor.getChild("Next").setVisible(true); 98 | this.actor.getChild("Reset").setVisible(false); 99 | } 100 | 101 | if(Sup.Input.wasKeyJustPressed("SPACE")){ 102 | this.actor.getChild("Next").setVisible(false); 103 | this.actor.getChild("Reset").setVisible(true); 104 | 105 | this.level.setTileMap("Levels/"+LEVELS[levelCount]); 106 | 107 | Game.setLevel(); 108 | } 109 | } 110 | // if R key is pressed and the level is NOT won, then reset the whole level 111 | if(Sup.Input.wasKeyJustPressed("R") && !isLevelWon){ 112 | Game.resetLevel(this.level.getTileMap()); 113 | } 114 | } 115 | 116 | [...] 117 | ``` 118 | 119 | Finally, we can make visible the Reset text in the Game scene and launch the game. It is finished. 120 | 121 | 122 | ### Adding sound and music to the game 123 | 124 | I will update this part later when I finish the music. 125 | 126 | 127 | ### Add new levels to the game 128 | 129 | It is easy to add a level to the game, we just need to duplicate the levelTemplate, 130 | change it name with a number like Level4 and don't forget to add as much boxes than target. 131 | 132 | Also it is important to add a player start position. 133 | 134 | Then we just need to add it to the LEVELS list in our main script like so : 135 | 136 | ```TypeScript 137 | [...] 138 | // List the game levels 139 | const LEVELS = { 140 | 0:'LevelTemplate', 141 | 1:'Level1', 142 | 2:'Level2', 143 | 3:'Level3', 144 | 4:'Level4' 145 | }; 146 | [...] 147 | ``` 148 | 149 | Note : If you need idea of sokoban puzzle, you can [check here](http://sokoban.info/). 150 | -------------------------------------------------------------------------------- /3SuperSokoban/ch6.md: -------------------------------------------------------------------------------- 1 | # SUPERPOWERS TUTORIAL #3 : SUPER SOKOBAN 2 | ## *Chapter 6 : Complete Game Source Reference* 3 | 4 | 5 | ### Assets files 6 | 7 | * character.png 8 | * next.png 9 | * reset.png 10 | * screen.png 11 | * tiles.png 12 | * scripts/player.ts 13 | * scripts/level.ts 14 | * scripts/main.ts 15 | 16 | 17 | 18 | ### Scripts source 19 | 20 | main.ts 21 | 22 | ```TypeScript 23 | [...] 24 | // List the game levels 25 | const LEVELS = { 26 | 0:'LevelTemplate', 27 | 1:'Level1', 28 | 2:'Level2', 29 | 3:'Level3' 30 | }; 31 | 32 | // List the map layers 33 | enum Layers{ 34 | World = 0, 35 | Actors = 1 36 | }; 37 | 38 | // List the map tiles 39 | enum Tiles{ 40 | Empty = -1, 41 | Wall = 0, 42 | Floor = 1, 43 | Target = 2, 44 | Crate = 3, 45 | Start = 4, 46 | Packet = 5 47 | }; 48 | 49 | // Game level won flag 50 | var isLevelWon : boolean = false; 51 | 52 | // Current Level, Start Level 1 53 | var levelCount : number = 1; 54 | 55 | // Number of level, checked when game awake 56 | var levelMax : number; 57 | 58 | 59 | // Original map pattern saved 60 | var mapSaved : number[][]; 61 | 62 | // Set new player position to map origin 63 | var playerPosition = new Sup.Math.Vector2(0, 0); 64 | 65 | namespace Game{ 66 | export function getMaxLevel(){ 67 | levelMax = 0; 68 | // Add one for each level in LEVELS 69 | for(let level in LEVELS){ 70 | levelMax++; 71 | } 72 | } 73 | 74 | export function getPosition(level){ 75 | /* 76 | Scan the 16x12 level in order to : 77 | - Save the tile pattern for each layer in the mapSaved array 78 | - Set the playerPosition vector from the Start tile position on Actor layer 79 | - Change the Start tile by an empty tile (the Player Sprite will come instead) 80 | */ 81 | 82 | // Set the variables to default, erase previous level 83 | mapSaved = []; 84 | playerPosition.x = 0, playerPosition.y = 0; 85 | 86 | for(let column = 0; column < 12; column++){ 87 | for(let row = 0; row < 16; row++){ 88 | 89 | // get the tile for x = row and y = column positions 90 | let actorTile = level.getTileAt(Layers.Actors, row, column); 91 | 92 | // Add the tile to the array 93 | mapSaved.push(actorTile); 94 | 95 | if(actorTile === Tiles.Start){ 96 | // remove the Start tile and replace with empty tile 97 | level.setTileAt(Layers.Actors, row, column, Tiles.Empty); 98 | 99 | // set position to x, y on level map 100 | playerPosition.add(row, column); 101 | } 102 | } 103 | } 104 | } 105 | 106 | export function checkLevel(level){ 107 | 108 | /* 109 | We check all the level for the crates and targets positions. 110 | We then compare if the position of crates and targets match, 111 | if all crate are on target, the level is won. 112 | */ 113 | 114 | let boxesNumber : number = 0; 115 | let boxesPositions = []; 116 | let targetsPositions = []; 117 | 118 | for(let column = 0; column < 12; column++){ 119 | for(let row = 0; row < 16; row++){ 120 | 121 | // Take the tiles from two layers to coordinates row and column 122 | let actorTile = level.getTileAt(Layers.Actors, row, column); 123 | let worldTile = level.getTileAt(Layers.World, row, column); 124 | 125 | // If the actor tile is a box, keep the position 126 | if(actorTile === Tiles.Crate || actorTile === Tiles.Packet){ 127 | let position = new Sup.Math.Vector2(row, column); 128 | boxesPositions.push(position); 129 | 130 | // we count the total number of crate 131 | boxesNumber++; 132 | } 133 | 134 | // If the world tile is a target, keep the position 135 | if(worldTile === Tiles.Target){ 136 | let position = new Sup.Math.Vector2(row, column); 137 | targetsPositions.push(position); 138 | } 139 | } 140 | } 141 | 142 | // Check if all boxes 143 | if(checkVictory(level, boxesNumber, boxesPositions, targetsPositions)){ 144 | isLevelWon = true; 145 | levelCount++; 146 | }; 147 | } 148 | 149 | function checkVictory(level, boxesNumber, boxesPositions, targetsPositions){ 150 | 151 | /* 152 | Check all the positions and find if the coordinate match together. 153 | If there is as much match than there is boxes, the game is finished. 154 | */ 155 | 156 | let count : number = 0; 157 | 158 | for(let posBox of boxesPositions){ 159 | for(let posTarget of targetsPositions){ 160 | if(posBox.x === posTarget.x && posBox.y === posTarget.y){ 161 | count++; 162 | } 163 | } 164 | } 165 | if(count === boxesNumber){ 166 | return true; 167 | } 168 | } 169 | 170 | export function setLevel(){ 171 | //reset values to default 172 | isLevelWon = false; 173 | //reload the scene 174 | Sup.loadScene("Game"); 175 | } 176 | 177 | export function resetLevel(level){ 178 | let index : number = 0; 179 | 180 | // set all the actor tiles of the current level to the savedMap tile 181 | for(let column = 0; column < 12; column++){ 182 | for(let row = 0; row < 16; row++){ 183 | level.setTileAt(Layers.Actors, row, column, mapSaved[index]); 184 | index++ 185 | } 186 | } 187 | // call the setLevel function to prepare a new level 188 | setLevel(); 189 | } 190 | } 191 | 192 | // Call the getMaxLevel function when game is launched 193 | Game.getMaxLevel(); 194 | [...] 195 | ``` 196 | 197 | player.ts 198 | 199 | ```TypeScript 200 | [...] 201 | class PlayerBehavior extends Sup.Behavior { 202 | 203 | start() { 204 | // set position of Player actor to the playerPosition 2D vector 205 | this.actor.setPosition(playerPosition); 206 | } 207 | 208 | move(x, y){ 209 | 210 | /* 211 | We update the future position of the player and start to check condition : 212 | - (1) if the next tile is an empty floor, the player can move, canMove is true 213 | - (2) else, canMove is false 214 | - (3) if the next tile is a box, then we check a second branch of condition : 215 | - (4) if the the next tile after the box tile is an empty floor, the player can push the box 216 | - (5) else, canMove is false 217 | - (6) if canMove is true, then we update the SpriteRenderer of the Player Actor to the new position 218 | - (7) else, we take back the previous coordinates for the player position 219 | */ 220 | 221 | let canMove: boolean; 222 | 223 | // Set to new coordinates 224 | playerPosition.add(x, y); 225 | 226 | // We get the tiles index for each layer of the map from the new coordinates 227 | let level = Sup.getActor("Level").tileMapRenderer.getTileMap(); 228 | let tileWorld = level.getTileAt(Layers.World, playerPosition.x, playerPosition.y); 229 | let tileActors = level.getTileAt(Layers.Actors, playerPosition.x, playerPosition.y); 230 | 231 | // (1) 232 | if(tileWorld === Tiles.Floor || tileWorld === Tiles.Target){ 233 | canMove = true; 234 | 235 | // (3) 236 | if(tileActors == Tiles.Crate || tileActors == Tiles.Packet){ 237 | 238 | // We get the tiles index for each layer of the map from the coordinates after the new ones 239 | let nextWorldTile = level.getTileAt(Layers.World, playerPosition.x + x, playerPosition.y + y); 240 | let nextActorsTile = level.getTileAt(Layers.Actors, playerPosition.x + x, playerPosition.y + y); 241 | 242 | // (4) 243 | if(nextWorldTile == Tiles.Floor && nextActorsTile == Tiles.Empty){ 244 | 245 | level.setTileAt(Layers.Actors, playerPosition.x, playerPosition.y, Tiles.Empty); 246 | // If the next world tile is floor, the box is a crate. 247 | level.setTileAt(Layers.Actors, playerPosition.x + x, playerPosition.y + y, Tiles.Crate); 248 | 249 | } 250 | // (4) 251 | else if(nextWorldTile == Tiles.Target && nextActorsTile == Tiles.Empty){ 252 | 253 | level.setTileAt(Layers.Actors, playerPosition.x, playerPosition.y, Tiles.Empty); 254 | // If the next world tile is a target tile, the box is a packet. 255 | level.setTileAt(Layers.Actors, playerPosition.x + x, playerPosition.y + y, Tiles.Packet); 256 | 257 | } 258 | // (5) 259 | else{ 260 | canMove = false; 261 | } 262 | } 263 | } 264 | // (2) 265 | else{ 266 | canMove = false; 267 | } 268 | 269 | // (6) 270 | if(canMove === true){ 271 | // We update the Player Actor with the new playerPosition vector coordinate 272 | this.actor.setPosition(playerPosition.x, playerPosition.y); 273 | 274 | Game.checkLevel(level); 275 | } 276 | // (7) 277 | else{ // blocked, return to previous value 278 | playerPosition.subtract(x, y); 279 | } 280 | } 281 | 282 | update() { 283 | // if the level is NOT won, the player have control 284 | if(!isLevelWon){ 285 | if(Sup.Input.wasKeyJustPressed("UP")){ 286 | this.move(0, 1); 287 | this.actor.spriteRenderer.setAnimation('up'); 288 | } 289 | else if(Sup.Input.wasKeyJustPressed("DOWN")){ 290 | this.move(0, -1); 291 | this.actor.spriteRenderer.setAnimation('down'); 292 | } 293 | else if(Sup.Input.wasKeyJustPressed("LEFT")){ 294 | this.move(-1, 0); 295 | this.actor.spriteRenderer.setAnimation('left'); 296 | } 297 | else if(Sup.Input.wasKeyJustPressed("RIGHT")){ 298 | this.move(1, 0); 299 | this.actor.spriteRenderer.setAnimation('right'); 300 | } 301 | } 302 | } 303 | } 304 | Sup.registerBehavior(PlayerBehavior); 305 | 306 | [...] 307 | ``` 308 | 309 | level.ts 310 | 311 | ```TypeScript 312 | [...] 313 | class LevelBehavior extends Sup.Behavior { 314 | 315 | level = this.actor.tileMapRenderer; 316 | 317 | awake() { 318 | // set Level actor to the current level map path 319 | this.level.setTileMap("Levels/"+LEVELS[levelCount]); 320 | 321 | // call the getPositions function with the current tile map as parameter 322 | Game.getPosition(this.level.getTileMap()); 323 | } 324 | 325 | update() { 326 | 327 | /* 328 | If the level is won we check 329 | - if it was the last level 330 | - if yes, we go to the victory screen 331 | - else we change the transition text visibility to true 332 | - if the key space is pressed 333 | - change the transition text visibility to false 334 | - change the new level tile map 335 | - call the setLevel function that will prepare the scene before to reload it 336 | */ 337 | 338 | if(isLevelWon){ 339 | if(levelCount == levelMax){ 340 | Sup.loadScene("Victory/Scene"); 341 | } 342 | else{ 343 | this.actor.getChild("Next").setVisible(true); 344 | this.actor.getChild("Reset").setVisible(false); 345 | } 346 | 347 | if(Sup.Input.wasKeyJustPressed("SPACE")){ 348 | this.actor.getChild("Next").setVisible(false); 349 | this.actor.getChild("Reset").setVisible(true); 350 | 351 | this.level.setTileMap("Levels/"+LEVELS[levelCount]); 352 | 353 | Game.setLevel(); 354 | } 355 | } 356 | // if R key is pressed and the level is NOT won, then reset the whole level 357 | if(Sup.Input.wasKeyJustPressed("R") && !isLevelWon){ 358 | Game.resetLevel(this.level.getTileMap()); 359 | } 360 | } 361 | } 362 | Sup.registerBehavior(LevelBehavior); 363 | 364 | [...] 365 | ``` 366 | -------------------------------------------------------------------------------- /3SuperSokoban/img/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/assets.png -------------------------------------------------------------------------------- /3SuperSokoban/img/character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/character.png -------------------------------------------------------------------------------- /3SuperSokoban/img/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/check.png -------------------------------------------------------------------------------- /3SuperSokoban/img/level1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/level1.png -------------------------------------------------------------------------------- /3SuperSokoban/img/level1actors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/level1actors.png -------------------------------------------------------------------------------- /3SuperSokoban/img/level1world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/level1world.png -------------------------------------------------------------------------------- /3SuperSokoban/img/level2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/level2.png -------------------------------------------------------------------------------- /3SuperSokoban/img/level3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/level3.png -------------------------------------------------------------------------------- /3SuperSokoban/img/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/next.png -------------------------------------------------------------------------------- /3SuperSokoban/img/scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/scene.png -------------------------------------------------------------------------------- /3SuperSokoban/img/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/settings.png -------------------------------------------------------------------------------- /3SuperSokoban/img/sokoban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/sokoban.png -------------------------------------------------------------------------------- /3SuperSokoban/img/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mseyne/superpowers-tutorials/e952dbbd46744b4b1006013bd8cce0010b1a4c1e/3SuperSokoban/img/template.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SUPERPOWERS TUTORIAL SERIES 2 | ### Learn creative development while revisiting video game history. 3 | 4 | * 1# Super Pong [Tutorial](1SuperPong) - [Source Assets][2] - [Source Project][3] - [Play the game][4] 5 | * 2# Super OXO [Tutorial](2SuperOXO) - [Source Assets][5] - [Source Project][6] - [Play the game][7] 6 | * 3# Super Sokoban [Tutorial](3SuperSokoban) - [Source Assets][8] - [Source Project][9] - [Play the game][10] 7 | * 4# Super Asteroids and Super Spacewar : 8 | [Tutorial on Github][15] - [Read on Gitbook][11] - [Source Assets][12] - [Source Project][13] - [Play the game][14] 9 | * 5# Super Pacman : 10 | [Tutorial on Github][16] - [Read on Gitbook][17] - [Source Assets][18] - [Source Project][19] - [Play the game][20] 11 | 12 | [Peer Production Licence][1] 13 | 14 | [1]: http://p2pfoundation.net/Peer_Production_License 15 | [2]: https://github.com/mseyne/superpowers-sources/tree/master/1SuperPong 16 | [3]: https://github.com/mseyne/superpowers-projects/tree/master/1SuperPong 17 | [4]: http://mseyne.itch.io/pong 18 | [5]: https://github.com/mseyne/superpowers-sources/tree/master/2SuperOXO 19 | [6]: https://github.com/mseyne/superpowers-projects/tree/master/2SuperOXO 20 | [7]: http://mseyne.itch.io/oxo 21 | [8]: https://github.com/mseyne/superpowers-sources/tree/master/3SuperSokoban 22 | [9]: https://github.com/mseyne/superpowers-projects/tree/master/3SuperSokoban 23 | [10]: http://mseyne.itch.io/sokoban 24 | [11]: https://www.gitbook.com/book/mseyne/super-asteroids-and-super-spacewar/details 25 | [12]: https://github.com/mseyne/superpowers-sources/tree/master/4SuperAsteroids 26 | [13]: https://github.com/mseyne/superpowers-projects/tree/master/4SuperAsteroids 27 | [14]: https://mseyne.itch.io/super-asteroids-and-super-spacewar 28 | [15]: https://github.com/mseyne/super-asteroids-super-spacewar 29 | [16]: https://github.com/mseyne/super-pacman/tree/master/en 30 | [17]: https://mseyne.gitbooks.io/super-pacman/content/en/index.html 31 | [18]: https://github.com/mseyne/superpowers-sources/tree/master/5SuperPacman 32 | [19]: https://github.com/mseyne/super-pacman-project 33 | [20]: https://mseyne.itch.io/super-pacman --------------------------------------------------------------------------------