Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

Newcomers AppGameKit Corner / Adding more spites, levels and fixing the score

Author
Message
KMCE
8
Years of Service
User Offline
Joined: 8th Apr 2016
Location:
Posted: 9th Apr 2016 04:00 Edited at: 9th Apr 2016 07:53
I have 3 questions. So I have a simple game in which a cat catches bed and gets caught by its owner. The cat gets caught when the carrier touches it 3 times, however when the score gets to 0 and the end screen comes up, there is an option to start the game again, however it starts from having 0 lives basically, i cannot work out how to fix that.

2, I want to add new enemy sprites after the cat has caught 10 beds, can someone link me to somewhere explaining how to do it, or offer some help

3. Also just looking for a link if possible, i have check the agk page for help but cant see anything, im looking to add new levels to my game, and help with that would be great.


Any help given i would greatly appreciated, thanks alot

Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 11th Apr 2016 03:18 Edited at: 11th Apr 2016 04:04
1: You're not doing the hierarchical pyramid structure I talked about in my answer to your previous question. what you want is a main loop that is essentially the start menu, that on each iteration resets score and everything else. Beneath that, you want the game loop as a separate function that takes care of the actual game, and when done, bounces back to the main menu loop. The game loop should beneath it have a series of other functions that it calls to do all the various tasks that need be done, like registering keyhits, moving sprites and so on.

2: When set up as described in point 1, in the game loop, you simply add a call to a function that checks for number of beds the cat have caught, and if over 10, adds another enemy sprite. This by the way should be set up so as to be a datatype that is declared as an array.

3: There is plenty of books on how to program, one even on how to program in AGK. Online naturally there are also plenty of resources for learning. I have no links as such, just google 'Structured / Procedural Programming Tutorial' or similar. What you find may not have code written in Basic, much less AppGameKit, but the principles are the same and are directly transferable.

I will however re-write your code (with plenty of comments explaining what I do and why) in the morning in such a way that points 1 and 2 are covered. Now I need my sleep
Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 11th Apr 2016 22:46 Edited at: 12th Apr 2016 00:06
Allright. First step, getting your code easy to read by ways of some consistent formatting rules. I've applied mine here, but you can come up with your own. Nothing in this regard is set in stone, the only thing that is important is that it is applied throughout your project. AppGameKit is quite forgiving with lower and upper case, so x is the same as X and so on. Other languages are not though, so may as well get into good habits now rather than later.

One note though: Indentation. Use it properly. It eases readability to no end.

With the formatting done with, I went ahead and got rid of all the 'gosub', labels and 'return' commands. Those things are evil and will in any larger project lead to an unholy mess of stagnant and brittle code.


(how code with arbitrary jump in and jump out points really looks like)

Now mind you this first reworking of your code probably won't compile. I've included it anyway so that you can follow more easily what I'm doing.



Having everything inside functions have a few advantages. First and foremost it encapsulates the code. It'll never be run by accident - it must be explicitly called. It also allows local variables that can not spill over to any other part of the code. In addition, you can pass arguments to a function, making it behave differently depending on what you send it. We'll see this in action pretty soon. Last, you can call it like any other command, and even use to assign values to a variable inside another function - or main for that matter.

Do note also that I got rid of most of the comments. If the code is easily readable, one doesn't need that many comments to begin with - and comments like // load images is quite a waste when the very next command is loadImage() ...

End of 1st installment. I'll keep these posts short and covering only a topic or two.

In the next post I'll be squatting most of the discrete variables and bunching them together in Data Types and Arrays.
Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 12th Apr 2016 00:33 Edited at: 12th Apr 2016 01:17
Ok, 2nd installment. Now we're getting closer to solving your problems with resetting scores and such, and also adding more enemies as the cat catches more beds (why not moving mice?)

Just about everything is moved from discrete (ie standalone) variables and into datatypes. On the global scale there is not a single variable to be seen - as it should be. There is one constant though, maxEnemies. this is now set to only 2, but I'll change this later.

This is set using the #constant method because later in the code you will see this is used to determine the lenght of two arrays. One array inside a datatype (spriteID.enemy[]), and one array made out of a datatype (enemy[]). When setting the size of an array and not wanting to use a number directly in the declaration, it need be a number set with the #constant method. We'll get back to these arrays later when getting to how to add more enemies to your game.

Note that with the datatypes I've set 'as integer' on them. Strictly speaking not necessary, as it defaults to integer unless you specify something else. But I like my declarations to be explicit, not implicit.

Also I've created three new functions.

---

1: spriteImageIDs()

Contains all the ID numbers for the sprites and images in the game

2: defaultGlobals()

contains all the starting values for the datatypes. This will become important later on when resetting after game-over.

3: getMedia()

Just to get all those loadImage() calls out of the way really. A tip here, is to have all the house-keeping stuff on a separate tab by creating a new empty .agk file that you import into your project using the #include command.

I collect all functions within a similar domain on such separate tabs. Like say all output related functions get their own document that I import and so on. Keeps things nice and tidy.

---

Anyhow, here is the code so far. Mind you, it won't compile just yet.



That's it for this 2nd. installment. In the next, I'll start rearranging stuff a bit and get rid of redundancies.
Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 12th Apr 2016 02:21 Edited at: 13th Apr 2016 11:42
Now that the basics are organized, we can start setting up the hierarchy of functions.

First of all, your main loop isn't anymore. I've pushed it into a function called 'gameLoop'. Then I've created a function called drawMainMenu - basically your gameintro subroutine, just, uhm, different.

Let's take a look at it:



So what is happening here? I've defined before the main loop a local variable the value of which will determine whether to exit the game or start the game. You only need define a variable as local if in the main part of your program - inside a function there is no need to do so, as they will be local by default.

Inside the main loop, the very first thing I do is have the default values loaded into the relevant datatypes. This by means of the 'defaultGlobals()' function that got made in the previous installment. This will repeat every time you are done with a game and are thrown back into the main menu. Hence solving your problem of getting the lives back to 3 and so on.

Next the variable 'choice' inherits its' value by what happens inside the drawMainMenu() function. To this same function I also send the sprite and image datatypes.

Then follows an if statement. When true, it will execute the gameLoop() function. When the gameLoop() function is done (ie Game Over) it resets the 'choice' variable to 0.

Last in the main loop is the condition for quitting the game alltogether.

Moving on to the drawMainMenu() function. In the definition of this, I create two local datatypes named spr and img. These are instances of the spriteID and imageID datatypes respectively, and inherit the values stored in the sprite and image datatypes that have been sent to it when it got called in the main loop.

A local variable is then made just to declare the integer variable 'key'. Not strictly speaking necessary, but as said, I like my declarations to be explicit.

This is followed by actually drawing the start screen with its' sprite and some text.

Then the game is essentially halted in a loop whilst waiting for the allowed user input. The variable 'key' gets its' value from the function getStartKey().

I'll not comment on the gameLoop() function much here. In the next installment I'll go over this and the changes that will be done to it.

Last function I'll comment now, is the getStartKey() function. This basically checks if the Escape key or Spacebar have been pressed. Or no key. It then returns the result whatever it may be.

---

The point here is to show how to distribute responsibility so that a function have as few as possible of them. Ideally only one. Two is fine near or at the peak of the pyramid, but never down low by the base.

On tier 1, The main loop have one responsibility - Determine to start or quit the game.

Beneath this in tier 2 is two functions, drawMainMenu() and gameLoop()

drawMainMenu() have two responsibilities - to draw and later clean up the startmenu. and work as an intermediate to polling and passing on what the player wants to do.

Tier 3 where so far we find the getStartKey() function. though it won't be lonely long. Whatever happens here, is pulled up the pyramid until it finds its' way to the big honcho - the main loop. Again, this function have but the one responsibility.

---

You can see the pyramid, the hierarchy starting to form. One more thing to note, is that I try to keep globals - the datatypes - away from being handled directly inside a function. Much better to pass the values on to a local instance of it. Not always possible if changes to the values are to be made. But we'll get back to that later on.

Anyhow, enough for this installment. Not much to go now
Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 12th Apr 2016 04:24 Edited at: 12th Apr 2016 15:39
Ok, let's take a closer look at the gameLoop() function, and do some cleanup. Actually, let's clean up first.

quit() function we won't need. It's gone. Same with enemychaser2().

This leaves us with a gameLoop() function that looks like this:



Only need to set the print color once, so is moved outside the loop. Got rid of setting the print size, since by using the printc command, I got score, catches and time text on the same line.

So time to think through what happens when. Originally the player (kitty) movement is last, it should be first. Then update enemy positions, then check for collisions, then update time. Last draw the sprites that can move and finally sync the screen.

first I'll poll the keyboard - reusing the getStartKey() function, but renamed to getKey() and with these changes:



Then pass the key onto the very changed movePlayer() function - which now also will check for collisions.

The changed movePlayer() function looks like this:



Here you'll see I moved the previous hardcoded offset into a variable instead, for easy change should you decide movement need be faster or slower later on. Also, I've introduced two new concepts. First the Select - Case construct. this works as a switch, or as a series of if statements operating on the same variable. It saves a bit of typing.

Speaking of saving typing, rather than kitty.y = kitty.y + offset, I've used inc to increase and dec to decrease by offset.

Then the kitty sprite get updated, and collision checks are tested for.

The entire code for keeping track of game-time have been replaced. This is the new gameTimer() function:



Some changes in the gameLoop() have been done to make the timer work properly. With all the changes, the new and improved gameLoop() function now look like this:



I'll provide here the code for a working version that is only missing the functionality of the enemies. I'll get to those in the next installment.



This will compile as is. Next up - after I've received confirmation you're on board so far and got it working, though perhaps with a question or two as to how and why, I'll get into how to spawn as many enemy sprites as you want and wrap this here up.

Until then
BatVink
Moderator
21
Years of Service
User Offline
Joined: 4th Apr 2003
Location: Gods own County, UK
Posted: 12th Apr 2016 09:05
I really hope KMCE comes back to this thread after all of your efforts!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Quidquid latine dictum sit, altum sonatur
TutCity is being rebuilt
KMCE
8
Years of Service
User Offline
Joined: 8th Apr 2016
Location:
Posted: 12th Apr 2016 21:52
wow,I didn't expect so much detail in a reply , since I am really new to coding I think I will have to reread it a few times before I come to understand it all. But I just wanted to thank you already, I really didn't expect a replay like that. I am really grateful.
Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 13th Apr 2016 03:05 Edited at: 13th Apr 2016 03:11
Why thank you - and it's my pleasure. We were all beginners once, and the best way to learn is to try and understand others code. Back in the day, computers came with reference manuals and there were magazines with program listings for one to type in and accompanying articles explaining what the program does and how.

Not so much of that these days - not to mention that most modern programming languages are either quite daunting to get into or are centered around that input-output device we call the web.

Which is what makes AppGameKit so great. With minimal effort, one can get something up and running - on multiple platforms - in next to no time. A great place for beginners to start, and for those of us more seasoned, to get something quite complex done in half the time and with a quarter of the pain than if done in any other language.

Anyhow, on with the show...

How to spawn more enemies in your game? Before I move on, I'll assume the reason you got two images for enemy (enemy.jpg and enemy2.jpg) is that these two images are slightly different - and you want new enemies to alternate between them. So odd numbered enemies have the one image, and even numbered enemies the other.

The rules were that for each 10 beds the cat catches, one more enemy spawns. For practical purposes I'll limit the max number of enemies to 10. Which is easily done by changing the constant maxEnemies from 2 to 10.

To keep track of how many are active, in the datatype controlGlobals I'll insert another field, let's call it enemyPointer, and give it the default value of 1 down in the defaultGlobals() function.

So a new function is needed, spawnEnemy(). This will check for even or odd number of new enemy and assign the correct sprite-image, and then position the new enemy at the default position. Finally, we'll modify the drawGameSprites() function that draws the initial sprites when a new game launches to use the spawnEnemy() function to draw and place the first enemy.

These two functions should now look like this:



Next up we need to modify the bedCollection() function a bit, so that for every 10 beds collected, another enemy spawn - to a max of 10. Before anything else, add the field bedCounter to the controlGlobals datatype, and set the default value to 0.



so when the bed counter reaches 10, and we have no more than max number of enemies active, the enemy pointer is increased by one, the bed counter is reset to zero and a new enemy is spawned.

Finally for this installment, making them move.

In the gameLoop() I call the enemyChaser() function that now look like this:



It does pretty much the same as the movePlayer() function - only inside a little loop so that all enemies get moved around.

That's it for now. I'll provide here the complete code in a working condition:



Next up, the getting caught code - and perhaps have a look at the movement of the enemies. The way they move now, they all eventually get stacked up on top of eachother, which is no good.
Dybing
13
Years of Service
User Offline
Joined: 12th Sep 2011
Location: Bergen, Norway
Posted: 13th Apr 2016 14:06 Edited at: 19th May 2016 00:55
OK, last installment.

First thing first, I got rid of all the globals. Globals can be dangerous and sort of defeats the purpose of the safety of encapsulation of code and variables that the functions provide.

As you've seen previously, you can pass a variable or datatype to a function so that it can do stuff with it and return a value.

eg:

---

first = 3
second = 4
result = add(first, second)

function add(a, b)

c = a + b

endFunction c

do
print(str(result))
sync()
loop

---

This will print 7

The problem is though, you can only return one variable from a function. Which is no good if you intend for a function to do many changes to a set of data.

This is where datatypes and arrays come in so handy. There are two ways to skin this particular cat.

You can use assignment as in this example:



This will print 7 and 12

The other method is to reference the original datatype when passing it into the function.

Like this:



Again this will print 7 and 12. Remove the reference however, and nothing is done to the datatype outside of the function, so it will return 0 and 0. The advantage of this method is that it'll keep the changes you make even if you pass into it several datatypes, unlike the first method where you can only return one datatype.

like so:



This prints 7, 12, 10 and 5. Having made changes to both the variables and results datatypes inside the function.

So why get rid of globals you may ask? Well, in a smaller project like yours, it doesn't matter much - it is quite managable. In bigger projects though you will soon create problems for yourself if using a plethora of globals. you'll create bugs you will spend the better part of a day if not more to chase down and squat, because a particular global variable is used throughout your project, and somewhere it is doing something it shouldn't that later breaks some other part of your code. This is called implicit coupling, and is something you want to avoid. All couplings should be explicit.

Using local variables over global ones, also stops you making silly mistakes. For instance say you got a global called controlFlag - this controls the state of a host of other functions. Then in one function you need a local variable to control a local flag. You name it controlFlag and assign it a value. Guess what happens? Your entire program breaks. this is called namespace pollution.

With local variables you can use the same name as often as you want in different functions without it affecting any other function. It is encapsulated. Globals however leak out all over the place and will likely cause problems somewhere down the line.

Ok, theory done with, back to your program and the task at hand - catching that cat

Your vetTime() function have been changed to the following:



Like when moving the enemies, the check for seeing if any enemy touches the cat is inside a for - next loop. If caught, the enemy is moved to a random position, number of catches (lives) is reduced and if it have reached zero, the GAME OVER screen is shown through the gameLose() function.

This as you can see returns a value that is stored in key, which in turn is sent up the pyramid of functions to gameLoop() where that value will exit gameLoop() and throw you back to the main menu.

A little look at gameLose() whilst we're at it:



Nothing too fancy going on here. It displays the game over graphics, and I added some text to it. Then it waits for user input - only one being allowed is to hit the escape key. This is done by once again calling the getKey() function. That is the beauty of functions, it saves you typing out a particular taks more than once. Reduced code redundancy makes for less work and fewer possibilities for bugs to creep into your code.

Here is the working code in full:



If you find something a bit baffling, try printing out the entire thing and follow it using a pencil to mark the jump out points and trace what happens to what where.

Happy coding
KMCE
8
Years of Service
User Offline
Joined: 8th Apr 2016
Location:
Posted: 14th Apr 2016 02:13
Hi again. Just wanted to thank you again. I am still reading through it all (still trying to understand most of it). I'm going to print out everything and read through it all (unfortunately I don't learn well from reading a computer screen, not great when trying to learn code ) but like i said I just wanted to thank you again for all the help you've given, really appreciate it .

Login to post a reply

Server time is: 2024-11-21 16:21:29
Your offset time is: 2024-11-21 16:21:29