How do you make a level?


(JohnnydCoder) #33

Thanks for the code!

I should have mentioned it instead of having you guess. Sorry. :slightly_smiling_face:

It will be a 16 by 16 size.


(Pharap) #34

Then you’ll be able to use the same collision code for moving enemies,
but it will need a small bit of adapting to be able to work with any entity.


(JohnnydCoder) #35

I created my own enemy collision function but my enemy is moving through the tiles that are solid.

Can you see if I did anything wrong in this code?

if (arduboy.everyXFrames(3))
    {
      if (enemy.isMovingL) 
      {
        enemy.x -= movementSpeed;
      }
      if (!enemy.isMovingL)
      {
        enemy.x += movementSpeed;
      }
      if (enemy.isFalling)
      {
       enemy.y -= gravitySpeed;
      }
   }
   
   this->updateEnemyPosition();
void updateEnemyPosition()
  {
    // Figure out the point that the enemy should be moving to
    int16_t newEnemyX = (this->enemy.x + this->movementSpeed);
    int16_t newEnemyY = (this->enemy.y + this->movementSpeed);
    
    // Figure out the tile coordinate that the player should be moving to
    const int16_t tileX = (newEnemyX / tileWidth);
    const int16_t tileY = (newEnemyY / tileHeight);

     // Find the x coordinate of the enemy's new right side
    const int16_t rightX = (newEnemyX + halfTileWidth);

    // Find which tile the enemy's new right side is in
    const int16_t rightTileX = (rightX / tileWidth);

    // Find the tile the enemy is trying to move into
    const TileType rightTile = map.getTile(rightTileX, tileY);

    // If the tile is solid
    if(isSolid(rightTile))
    {
      // Adjust the enemy's position to prevent collision
      newEnemyX = ((rightTileX * tileWidth) - halfTileWidth);
    }
    
    // Find the x coordinate of the player's new left side
    const int16_t leftX = ((newEnemyX - halfTileWidth) - 1);
    
    // Find which tile the player's new left side is in
    const int16_t leftTileX = (leftX / tileWidth);

    // Find the tile the player is trying to move into
    const TileType leftTile = map.getTile(leftTileX, tileY);

    // If the tile is solid
    if(isSolid(leftTile))
    {
      // Adjust the player's position to prevent collision
      newEnemyX = (((leftTileX + 1) * tileWidth) + halfTileWidth);
    }
  }

I sort of copied your code and adapted it to an enemy type of AI.

Thanks!


(Pharap) #36

Yeah, a few problems.

Firstly the collision checking result is being ignored.
(You calculate newEnemyX and newEnemyY, then do nothing with them.)

Secondly you’re adding ‘movementSpeed’ to both x and y,
which means you’ll move the enemy diagonally all the time:

What you need to be doing is giving enemy an xVelocity and a yVelocity,
setting xVelocity to what is currently movementSpeed and setting yVelocity to gravitySpeed.

You could reuse the Entity class provided in TileWorld to represent enemies.
Then you could change void updatePlayerPosition() to void updateEntityPosition(Entity & entity) and use that to handle collisions for all entities.

Like so:

If you’re wondering what & does, that make entity a reference.
It allows entity to be modified by the function.
Without it, entity would be copied and only the copy would be modified.

After making that change:

  • enemy.xVelocity = movementSpeed; makes the enemy move right
  • enemy.xVelocity = -movementSpeed; makes the enemy move left
  • enemy.yVelocity = gravitySpeed; applies gravity to the enemy

You just have to remember to do this->updateEntityPosition(enemy) to make the enemy actually move and do collision checking.


(JohnnydCoder) #37

I did what you told me to do about the function updateEntityPosition() but the opponent still isn’t responding to the world around it. Am I missing something?

Here’s the updated game.h code:

#pragma once

//
// Copyright (C) 2019 Pharap (@Pharap)
// 
// Modifications Copyright (C) 2019 Johnnydb (@JohnnydCoder)
// 
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <Arduboy2.h>
#include <stdint.h>
#include <avr/pgmspace.h>

#include "TileImages.h"
#include "TileType.h"
#include "WorldData.h"
#include "Entity.h"
#include "Camera.h"

const unsigned char panda[] PROGMEM =
{
  0x06, 0x09, 0x09, 0xf6, 0x01, 0x01, 0x01, 0x01, 0x1d, 0x3d, 0x35, 0x3d, 0x01, 0xfe, 0x48, 0x30,
0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xe1, 0xed, 0xdb, 0xd3, 0xd7, 0xd7, 0xef, 0xff, 0xfe, 0x00,
0x00, 0x00, 0x00, 0x7f, 0x83, 0x83, 0x9f, 0xa1, 0xc1, 0x01, 0xff, 0x83, 0x83, 0xbf, 0xa0, 0xc0,

};

class Game
{
private:
  static constexpr int16_t movementSpeed = 1;
  static constexpr int16_t gravitySpeed = 1;

  static constexpr int16_t centreScreenX = (WIDTH / 2);
  static constexpr int16_t centreScreenY = (HEIGHT / 2);

  static constexpr int16_t halfTileWidth = (tileWidth / 2);
  static constexpr int16_t halfTileHeight = (tileHeight / 2);
  
  static constexpr int16_t playerWidth = 16;
  static constexpr int16_t playerHeight = 24;

  static constexpr int16_t halfPlayerWidth = (playerWidth / 2);
  static constexpr int16_t halfPlayerHeight = (playerHeight / 2);

private:
  Arduboy2 arduboy;
  Map map;
  Camera camera;
  Entity playerEntity { centreScreenX, 0, 0, 0 };
  Entity enemy { centreScreenX + playerWidth, 32, 0, 0};

public:
  void setup()
  {
    this->arduboy.begin();

    // Load map
    this->loadMapFromProgmem(map0);
  }

  void loop()
  {
    if(!this->arduboy.nextFrame())
      return;

    this->arduboy.pollButtons();

    this->arduboy.clear();

    // Update and render
    this->updateGameplay();
    this->renderGameplay();

    this->arduboy.display();
  }

  void updateGameplay()
  {
    // Handle xVelocity
    int16_t xVelocity = 0;

    if(this->arduboy.pressed(LEFT_BUTTON))
    {
      xVelocity -= movementSpeed;
    }

    if(this->arduboy.pressed(RIGHT_BUTTON))
    {
      xVelocity += movementSpeed;
    }

    this->playerEntity.xVelocity = xVelocity;

    // Handle jumping (yVelocity)
    if(this->arduboy.justPressed(A_BUTTON))
    {
      if(this->playerEntity.yVelocity > 0)
        this->playerEntity.yVelocity = -9;
    }

    // If player is jumping
    if(this->playerEntity.yVelocity < 0)
    {
      // Slowly reduce negative velocity with gravity
      this->playerEntity.yVelocity += gravitySpeed;
    }
    else
    {
      // Else apply normal gravity
      this->playerEntity.yVelocity = gravitySpeed;
    }

    // Update the player's position
    this->updateEntityPosition(this->playerEntity);

    // Figure out the new map position based on the player's current position
    const int16_t newMapX = (this->playerEntity.x - centreScreenX);
    const int16_t newMapY = (this->playerEntity.y - centreScreenY);

    this->camera.x = ((newMapX < 0) ? 0 : newMapX);
    this->camera.y = ((newMapY < 0) ? 0 : newMapY);

if (enemy.x < playerEntity.x)
    {
      enemy.xVelocity = movementSpeed;
    }
     if (enemy.x > playerEntity.x)
    {
      enemy.xVelocity = -movementSpeed;
    }

    this->updateEntityPosition(this->enemy);
 };
 
  void drawEntities()
  {
    constexpr int16_t playerDrawOffsetX = (halfTileWidth + (playerWidth - tileWidth));
    constexpr int16_t playerDrawOffsetY = (halfTileHeight + (playerHeight - tileHeight));
  
    const int16_t x = ((this->playerEntity.x - playerDrawOffsetX) - this->camera.x);
    const int16_t y = ((this->playerEntity.y - playerDrawOffsetY) - this->camera.y);

    this->arduboy.fillRect(x, y, playerWidth, playerHeight, WHITE);
    this->arduboy.drawBitmap(x, y, panda, playerWidth, playerHeight, BLACK);

    arduboy.fillRect(enemy.x, enemy.y, 16, 16, BLACK);
  }

  void updateEntityPosition(Entity & entity)
  {
    // Figure out the point that the player should be moving to
   int16_t newX = (entity.x + entity.xVelocity);
   int16_t newY = (entity.y + entity.yVelocity);

    // Figure out the tile coordinate that the player should be moving to
    const int16_t tileX = (newX / tileWidth);
    const int16_t tileY = (newY / tileHeight);

    // Find the x coordinate of the player's new right side
    const int16_t rightX = (newX + halfTileWidth);

    // Find which tile the player's new right side is in
    const int16_t rightTileX = (rightX / tileWidth);

    // Find the tile the player is trying to move into
    const TileType rightTile = map.getTile(rightTileX, tileY);

    // If the tile is solid
    if(isSolid(rightTile))
    {
      // Adjust the player's position to prevent collision
      newX = ((rightTileX * tileWidth) - halfTileWidth);
    }
    
    // Find the x coordinate of the player's new left side
    const int16_t leftX = ((newX - halfTileWidth) - 1);
    
    // Find which tile the player's new left side is in
    const int16_t leftTileX = (leftX / tileWidth);

    // Find the tile the player is trying to move into
    const TileType leftTile = map.getTile(leftTileX, tileY);

    // If the tile is solid
    if(isSolid(leftTile))
    {
      // Adjust the player's position to prevent collision
      newX = (((leftTileX + 1) * tileWidth) + halfTileWidth);
    }

    // Find the x coordinate of the player's new bottom side
    const int16_t bottomY = (newY + halfTileHeight);

    // Find which tile the player's new bottom side is in
    const int16_t bottomTileY = (bottomY / tileHeight);

    // Find the tile the player is trying to move into
    const TileType bottomTile = map.getTile(tileX, bottomTileY);

    if(isSolid(bottomTile))
    {
      // Adjust the player's position to prevent collision
      newY = ((bottomTileY * tileHeight) - halfTileHeight);
    }

    // Find the x coordinate of the player's new top side
    const int16_t topY = ((newY - halfTileHeight) - 1);

    // Find which tile the player's new top side is in
    const int16_t topTileY = (topY / tileHeight);

    // Find the tile the player is trying to move into
    const TileType topTile = map.getTile(tileX, topTileY);

    // If the tile is solid
    if(isSolid(topTile))
    {
      // Adjust the player's position to prevent collision
      newY = (((topTileY + 1) * tileHeight) + halfTileHeight);
    }

    // Assign the player's new position
    // Whilst preventing the position from going out of bounds
    entity.x = ((newX > halfTileHeight) ? newX : halfTileWidth);
    entity.y = ((newY > halfTileHeight) ? newY : halfTileHeight);
  }
  void renderGameplay()
  {
    // Draw map
    this->drawMap(this->camera.x, this->camera.y, this->map);

    // Draw player
    this->drawEntities();

    // Print camera position
    this->arduboy.print(this->camera.x);
    this->arduboy.print(F(", "));
    this->arduboy.println(this->camera.y);

    // Printer player entity position
    this->arduboy.print(this->playerEntity.x);
    this->arduboy.print(F(", "));
    this->arduboy.println(this->playerEntity.y);
  }

  void loadMapFromProgmem(const Map & progmemMap)
  {
    // Copy map from progmem into map
    memcpy_P(&this->map, &progmemMap, sizeof(Map));
  }

  void drawMap(int16_t mapX, int16_t mapY, Map map)
  {
    constexpr size_t screenTileWidth = ((WIDTH / tileWidth) + 1);
    constexpr size_t screenTileHeight = ((HEIGHT / tileHeight) + 1);

    for(uint8_t y = 0; y < screenTileHeight; ++y)
    {
      const int16_t mapTileY = (mapY / tileHeight);
      const int16_t tileY = (mapTileY + y);
  
      // If tile is out of bounds, continue next iteration
      if(tileY < 0 || tileY >= map.getHeight())
        continue;
  
      const int16_t mapRemainderY = (mapY % tileHeight);
      const int16_t drawY = ((y * tileHeight) - mapRemainderY);
  
      for(uint8_t x = 0; x < screenTileWidth; ++x)
      {
        const int16_t mapTileX = (mapX / tileWidth);
        const int16_t tileX = (mapTileX + x);
  
        // If tile is out of bounds, continue next iteration
        if(tileX < 0 || tileX >= map.getWidth())
          continue;
  
        const int16_t mapRemainderX = (mapX % tileWidth); 
        const int16_t drawX = ((x * tileWidth) - mapRemainderX);

        // Get tile type from map
        TileType tileType = map.getTile(tileX, tileY);

        // Draw tile
        Sprites::drawOverwrite(drawX, drawY, tileImages, getTileIndex(tileType));
      }
    }
  }
};

One more question: is there any way to place enemies specifically on one part of a map?


(Pharap) #38

There’s several problems:

  • The enemy isn’t being drawn in the correct place.
    • The x and y used by entities is actually the centre of the entity’s collision box, not the top left
  • The enemy isn’t being affected by gravity, so it just floats.
  • The enemy starts too close to the player, so it just snaps straight to the player and hangs there.

And with panda graphics:

When you create the enemy you’re already providing it with an x and a y position.


Of course, the enemy doesn’t currently jump.
Decent AI is very hard to program.

Some suggestions for what to do next:

You might want to lower the enemy’s speed if it’s going to home in on the player like that, otherwise it’s going to be almost impossible to avoid (unless you give the player a ‘run’ button or something).

You might want to stop the enemies chasing after the player if they go offscreen or if they’re far enough away from the player.

Find out how masks work so you can switch to drawing the panda with Sprites::drawPlusMask or Sprites::drawExternalMask.


I think you can already see that platformers are a lot more complicated than they get given credit for.


(JohnnydCoder) #39

I meant to say is there a way to make generate enemies anywhere (like make multiple enemies appear on a course).

Also, is there any way I can make the enemy turn around when it walks into a wall?

Here is a drawing of what I mean if that helps:

| <-O then | O->


(Pharap) #40

Yes, you’d need an array of enemies.

Yes, I know what you mean.
In technical terms you want to “invert their horizontal velocity upon collision with a solid tile”.

But it will either mean giving enemies different collision code or adding some other new functions.

I’ll have to demonstrate tomorrow, I don’t have time at the moment because it’s late here.


If you want to, while I’m away have a read of chapters 6.1-6.3 of the ‘learncpp’ tutorial and see if you can figure out the enemy thing on your own.

Don’t worry if you can’t, I’ll show you when I have chance,
but if you can figure at least some of it out then that’ll be a bit of extra progress.


(Pharap) #41

I didn’t have chance to do anything yesterday.
I might have some time later to write something.
I’ll focus on the collision code first rather than the multiple enemies.

Did you make any progress with the array tutorials?


Also, will your player character be animated?
And do you have the original sprite image?


(Pharap) #42

Actually, now I think about it, if an enemy turns around after walking into a wall, then it can no longer chase the player at the same time.

The two things are conflicting - chasing the player might involve walking into a wall, and then turning around means the enemy is no longer chasing the player.

So should the enemy not chase the player?
Or should the enemy chase the player until it hits a wall and then stop chasing the player?


I have written the code for having multiple enemies, but I won’t post it yet.
I think it would be good to see if you can manage it on your own first, since it’s a relatively small change.


(JohnnydCoder) #43

@Pharap,

Yes, I think I did but my code is getting errors when I compile.

Yes, it will be animated. I have the original sprite image. Do you want me to send it to you?

The enemy should not chase the player.

Thanks for finding those tutorials!


(Pharap) #44

I could have a look if you’d like, I can probably explain what’s going wrong.

I should have mentioned that some of the examples won’t work on Arduboy because they require the C++ standard library and the C++ standard library isn’t available for Arduino boards.

If you don’t want to install an IDE but you want to test some of the examples,
then you can use an online compiler like this one:
https://rextester.com/l/cpp_online_compiler_gcc

I was planning to create a mask for it so it can be drawn properly with Sprites.

If you want me to make the mask then I’ll need the original image,
or you could have a go at doing that yourself.

Ok, that makes more sense.

I have that tutorial and many other useful documents stored in my ResourceCollection repo:


(JohnnydCoder) #45

[quote=“Pharap, post:44, topic:7258”]
I could have a look if you’d like, I can probably explain what’s going wrong.
[/quote] Here is the code:

Entity enemies[2];
  Entity::enemies[0].x = tileWidth * 7;
  Entity::enemies[1].x = tileWidth * 10;
  Entity::enemies[2].x = tileWidth * 12;

  Entity::enemies[0].y = tileWidth * 7;
  Entity::enemies[1].y = tileWidth * 7;
  Entity::enemies[2].y = tileWidth * 7;

...

void drawEnemy()
  {
    constexpr int16_t enemyDrawOffsetX = (halfTileWidth + (enemyWidth - tileWidth));
    constexpr int16_t enemyDrawOffsetY = (halfTileHeight + (enemyHeight - tileHeight));
  
    const int16_t x = ((this->enemy.x - enemyDrawOffsetX) - this->camera.x);
    const int16_t y = ((this->enemy.y - enemyDrawOffsetY) - this->camera.y);

    this->arduboy.fillRect(x, y, enemyWidth, enemyHeight, BLACK);
    this->arduboy.fillRect(enemies[0].x, enemies[0].y, enemyWidth, enemyHeight, BLACK);
    this->arduboy.fillRect(enemies[1].x, enemies[1].y, enemyWidth, enemyHeight, BLACK);
    this->arduboy.fillRect(enemies[2].x, enemies[2].y, enemyWidth, enemyHeight, BLACK);

The error I’m getting is 'enemies' in 'struct Entity' does not name a type.


(Pharap) #46

You’ve got three problems.

Firstly you don’t need to put Entity:: in front of enemies.
:: is the ‘scope resolution operator’, you only use it when you need to access something that’s inside a different ‘scope’, e.g. something inside a namespace.
For example:

namespace Stuff
{
	constexpr unsigned int value = 10;
}

void someFunction()
{
	// Must use 'Stuff::' because this is outside the 'Stuff' namespace
	unsigned int value = Stuff::value;
}

Secondly, Entity enemies[2]; gives you 2 enemies, not 3.
I.e. you would have an enemies[0] and an enemies[1], but not an enemies[2].
If you wanted three enemies, you’d have to declare the array as Entity enemies[3];.

Thirdly, you can only access the elements of an array within a function,
you can’t access them from the global scope or function scope.
To initialise an array of enemies you’d have to write one of the following:

// Option 1:
Entity enemies[3]
{
	{ tileWidth * 7, tileWidth * 7, 0, 0 },
	{ tileWidth * 10, tileWidth * 7, 0, 0 },
	{ tileWidth * 12, tileWidth * 7, 0, 0 },
};

// Option 2:
Entity enemies[3] =
{
	{ tileWidth * 7, tileWidth * 7, 0, 0 },
	{ tileWidth * 10, tileWidth * 7, 0, 0 },
	{ tileWidth * 12, tileWidth * 7, 0, 0 },
};

(If Entity had some constructors then there would be some other ways as well.)


The drawEnemy function will compile, but it won’t do what you want it to.

I suggest reading about for loops if you don’t already know about them.
https://www.learncpp.com/cpp-tutorial/57-for-statements/

If you can’t figure it out then I’ll show you the solution I chose.


(JohnnydCoder) #47

I don’t know if I can figure it out, but at least I tried. :grinning:

Could you show me your solution @Pharap?


(Pharap) #48

Essentially you keep all the code that handles a single enemy and modify it so that rather than working on this->enemy it works on a reference parameter Entity & enemy.
Then you have an array of enemies that you loop through with a for loop and operate on each enemy individually using the code that was previously operating on this->enemy.

So basically you’re doing exactly what you were before, but instead of just operating on this->enemy, you’re running the same code for every enemy in the enemies array.

Does this make sense?
Is there anything you don’t understand?
(E.g. what a ‘reference’ parameter is.)


I haven’t written the code for moving the enemies back and forth yet because I was waiting to see if you could figure out the array part.

I might have time to write that tomorrow.


By the way, is the main goal of this for you to learn how to make games or is the main goal to get your game up and running?


(JohnnydCoder) #49

Thanks @Pharap!

Could you tell me what a reference parameter is?

The main goal is to learn how to make games, not just to get it up and running.


(Pharap) #50

Ok, good to know.

I had been thinking about suggesting that maybe I should just do all the programming if you were just wanting to get the game finished, but if the learning is the main goal then we’ll just keep going like this for now.

Though if learning is the main goal, I’d suggest maybe trying to write a few simpler games/programs first.
Platformers aren’t as easy as they seem.

I’ll explain and write some programs that demonstrate how references behave.
(As usual the explanation’s a bit long because I tend to go into detail about things.)

Firstly, here’s a program to demonstrate reference parameters in a function:
(Please read the comments as well as running it.)

#include <Arduboy2.h>

// Pass by value, does not modify value
// Works by making a copy of the argument
void incorrectAdd5(int value)
{
	value += 5;
}

// Pass by reference, does modify value
// Works by directly referencing the argument
void correctAdd5(int & value)
{
	value += 5;
}

Arduboy2 arduboy;

void setup()
{
	arduboy.begin();
}

void loop()
{
	if(!arduboy.nextFrame())
		return;
		
	arduboy.clear();
	
	int value = 10;
	
	arduboy.println(value);
	
	// Pass by value, does not modify value
	incorrectAdd5(value);

	// Prints 10 because value wasn't modified
	arduboy.println(value);
	
	// Pass by reference, does modify value
	correctAdd5(value);

	// Prints 16 because value wa properly modified
	arduboy.println(value);
	
	arduboy.display();
}

Basically, when you normally pass an argument to a function,
the function actually makes a copy of that value.
That means that if a function’s parameter is modified in any way within the function it doesn’t affect the original value.

However, if the parameter is a reference parameter,
instead of copying the value, the function actually references the original value,
which means that if the function modified the parameter it will affect the original value.

In the case of the platformer’s updateEnemy function,
it’s important that the update function can modify the entity passed to it,
hence why a reference parameter is needed.

In the case of drawEnemy, technically the argument doesn’t have to be a reference,
but sometimes a reference is cheaper because it avoids making a copy and copies can be expensive for larger values/objects.
In this case I didn’t check, so drawEnemy might have been fine with a non-reference parameter (i.e. drawEnemy(Entity enemy)).

While I’m mentioning drawEnemy I’d also like to mention const references.

A const reference is a reference that a function can’t modify.
It’s important to know when an object/variable will/won’t be modified,
and in general functions should avoid modifying their parameters (because it’s easier to understand code when functions don’t modify their parameters).

Thus const references are used when you don’t want to modify a parameter but you still want to avoid copying the object.
(E.g. drawEnemy would be a good candidate for using a const reference because it doensn’t (and shouldn’t) modify enemy. So really it should be drawEnemy(const Entity & enemy).)

As for when to use a const reference vs a non-reference parameter…
On a desktop the rule of thumb is:

  • For a fundamental type (int, uint8_t, bool etc.), pass by value
  • For a class/struct type, pass by const reference (unless to need to modify the argument)

(There’s actually another part to the rule involving things called ‘rvalue references’, but that’s more advanced stuff and they’re not very useful on Arduboy anyway.)

On the Arduboy I don’t think it makes much difference most of the time because it’s rare to be handling large objects so you don’t need to worry too much.

Edit:
I did end up testing drawEnemy with both a const reference and pass by value.
They used the same amount of progmem, which I suspect is because the function is being inlined, so there’s actually no parameter passing happening at all.
(The compiler is free to inline functions if they’re only used 1-3 times or they’re significantly small.)
I decided to change the code to use a const reference anyway though.


I did say programs (plural), so here’s the second program.
This time I’m demonstrating reference variables.
Basically it’s the same idea - making a reference to something rather than copying it.

#include <Arduboy2.h>

Arduboy2 arduboy;

void setup()
{
	arduboy.begin();
}

void loop()
{
	if(!arduboy.nextFrame())
		return;
		
	arduboy.clear();
	
	int object = 12;
	
	// Makes a copy of 'object'
	int objectCopy = object;
	
	// Makes a reference to 'object'
	int & objectRef = object;
	
	arduboy.println(object);	
	arduboy.println(objectCopy);	
	arduboy.println(objectRef);
	
	object = 15;
	
	arduboy.println(object);
	
	// objectCopy hasn't changed because it's a separate value
	arduboy.println(objectCopy);	
	
	// But objectRef has changed because it's just referencing 'object'
	arduboy.println(objectRef);
	
	// Changing 'objectRef' actually changes 'object'
	objectRef = 20;
	
	// 'object' has been modified through 'objectRef'
	arduboy.println(object);
	
	// objectCopy hasn't changed because it's a separate value
	arduboy.println(objectCopy);	
	
	arduboy.println(objectRef);
	
	arduboy.display();
}

Hopefully it makes sense.
Like I say, the idea is exactly the same, except this time there’s no function involved, just variables.

Two last things to say about references:

  • A reference cannnot be ‘repointed’ to a different object - they are bound to precisely one object and refer to that object for the rest of their ‘lifetime’
  • A reference must always refer to a valid object, there is no ‘null reference’

For these two reasons, references are almost always a much better choice than pointers.
(There are some very specific cases where pointers can do things references can’t.)


Edit
I have got the enemies moving back and forth now,
but I’ll wait until you’ve understood references before I upload that.
You might also have some more questions before then.
(Questions are important for learning, you can never ask too many.)


(JohnnydCoder) #51

So basically a reference parameter references a value and can therefore change that value, but if there isn’t a reference parameter, it just copies it and the value doesn’t change, correct?

What games would you suggest?


(Pharap) #52

Precisely.

(Technically it references an ‘object’ rather than a value, but I have yet to find something that adequately explains the difference between an object and a value, so I’m going assume they’re the same thing until I find a decent explanation.)

Although it’s not as fun, I’d suggest trying to write noughts and crosses first, purely because it will help you get used to using arrays (and possibly enums if you use an enum class to represent the cells).

Then maybe write a shooter like Asteroids or Spacewar (or a side-scrolling shooter) to get used to doing basic collisions and movement.

(To be clear, I’m not suggesting you abandon the platformer idea, you can always come back to it later, but for now I think you’re lacking the skills needed and making some other games will help you learn those skills.)