Discord
Login
Community
DARK THEME

How can i start using classes

i've read some of the classes questions here and I'm still confused a little bit with how they work. I understand objects to an extent and i see how classes are similar to objects. the biggest confusion i am having is how to use classes, the functions inside of classes and how to use the constructor function

I got you, classes are an amazing tool in microScript 2.0

Classes in microScript are incredibly powerful. Classes are essentially the blueprints or templates that you use to create objects. Think of it this way:

  • Class: The design plan for a car (e.g., "SportsCar Blueprint"). It defines what a SportsCar has (color, speed, make) and what it can do (drive, brake, accelerate).
  • Object (Instance): An actual car built from that blueprint (e.g., "My Red SportsCar" or "Tiberius's Blue SportsCar"). Each car has its own color, speed, and make, but they all follow the same blueprint.

I will break down for you how to use classes, their functions (methods), and the constructor function in microScript.

1. Defining a Class

You define a class using the class ... end block. The class itself is assigned to a variable name.

// This is our blueprint for a generic 'Entity' in a game
Entity = class
  // Properties and methods will go here
end

2. Properties (Variables within a Class)

Properties are variables that belong to an object created from a class. They define the object's state. You can initialize them in the constructor or dynamically add them later.

3. Methods (Functions within a Class)

Functions defined inside a class are called methods. They define what an object of that class can do. When you define a method, you typically refer to the object's own properties using the this keyword. this refers to the current instance of the class.

Entity = class
  x = 0 // Default property values
  y = 0
  speed = 1

  // A method to move the entity
  move = function(dx, dy)
    this.x = this.x + dx
    this.y = this.y + dy
    system.log("Entity moved to: " + this.x + ", " + this.y)
  end

  // A method to describe the entity's position
  getPosition = function()
    return "Entity is at (" + this.x + ", " + this.y + ")"
  end
end

Key point about this: When move() or getPosition() is called on an Entity object, this inside that method automatically refers to that specific Entity object.

4. The Constructor Function

The constructor is a special method that gets called automatically every time you create a new object (instance) from a class. Its primary purpose is to:

  1. Initialize the object's properties: Set initial values for x, y, health, name, etc.
  2. Perform any setup: Load assets, set up event listeners, etc.

You define it as a method named constructor.

Entity = class
  x = 0
  y = 0
  speed = 1
  name = "Unnamed Entity"

  // The constructor function
  constructor = function(initial_x, initial_y, entity_name)
    // 'this' refers to the new object being created
    this.x = initial_x or 0        // Use 'or 0' for default if not provided
    this.y = initial_y or 0
    this.name = entity_name or "Unnamed Entity"
    system.log("New Entity '" + this.name + "' created at: " + this.x + ", " + this.y)
  end

  move = function(dx, dy)
    this.x = this.x + dx
    this.y = this.y + dy
    system.log(this.name + " moved to: " + this.x + ", " + this.y)
  end

  getPosition = function()
    return this.name + " is at (" + this.x + ", " + this.y + ")"
  end
end

Using super() in Constructors (for Inheritance)

If your class extends (inherits from) another class, you'll often need to call the parent class's constructor. You do this with super().

// Let's create a more specific type of Entity
Player = class extends Entity
  // Player-specific properties
  score = 0

  constructor = function(initial_x, initial_y, player_name, initial_score)
    // IMPORTANT: Call the parent (Entity) constructor first!
    super(initial_x, initial_y, player_name)

    // Now initialize Player-specific properties
    this.score = initial_score or 0
    system.log(this.name + " (Player) created with score: " + this.score)
  end

  // Player-specific method
  addScore = function(amount)
    this.score = this.score + amount
    system.log(this.name + " score is now: " + this.score)
  end
end

5. Creating Objects (Instances) from a Class

Once you have a class defined, you create an object from it using the class name followed by parentheses (), passing any arguments the constructor expects.

// Creating instances of Entity
local entity1 = new Entity(10, 20, "Enemy_01") // Calls the Entity constructor
local entity2 = new Entity(-5, 8, "Item_02")  // Calls the Entity constructor with different values

// Creating an instance of Player
local player1 = new Player(0, 0, "Hero", 100) // Calls the Player constructor (which calls Entity's)

Notice the new keyword. While microScript is flexible and Entity(args) would also work for calling the constructor, new Entity(args) is the more standard and clearer way to signify that you are creating a new instance.

Putting It All Together: A Complete microScript Example

Here's a self-contained example you can run in microStudio to see it in action.

// This is our base class: Entity
// It defines common properties and behaviors for anything that exists in our game world
Entity = class
  x = 0 // Horizontal position
  y = 0 // Vertical position
  name = "Unnamed Entity" // A unique name for identification
  is_active = true // Whether the entity is currently active

  // The constructor is called when a new Entity object is created.
  // It's used to set up initial values for the object's properties.
  constructor = function(initial_x, initial_y, entity_name)
    // 'this' refers to the specific Entity object being created.
    this.x = initial_x or 0 // If initial_x is not provided, default to 0
    this.y = initial_y or 0 // If initial_y is not provided, default to 0
    this.name = entity_name or "Unnamed Entity"
    system.log("LOG: Initializing " + this.name + " at (" + this.x + ", " + this.y + ")")
  end

  // This is a method (a function belonging to the class)
  // It allows the entity to move itself by a given amount.
  move = function(dx, dy)
    this.x = this.x + dx
    this.y = this.y + dy
    system.log("LOG: " + this.name + " moved to (" + this.x + ", " + this.y + ")")
  end

  // Another method to get the entity's current position as a string.
  getPositionString = function()
    return this.name + " is currently at (" + this.x + ", " + this.y + ")."
  end

  // A method to draw the entity (just a simple circle for demonstration)
  draw = function()
    if this.is_active then
      screen.setColor("yellow")
      screen.fillCircle(this.x, this.y, 5) // Draw a small circle
      screen.setColor("white")
      screen.drawText(this.name, this.x, this.y - 10, 8) // Label the entity
    end
  end
end

// This is our derived class: Player
// It inherits all properties and methods from Entity, and adds its own specific ones.
Player = class extends Entity
  score = 0 // Player-specific property: score
  color = "blue" // Player-specific property: color

  // The Player constructor.
  // Note that it takes additional arguments specific to a Player.
  constructor = function(initial_x, initial_y, player_name, initial_score, player_color)
    // IMPORTANT: Call the parent class's constructor using 'super()'.
    // This ensures that the Entity part of the Player object is properly initialized.
    super(initial_x, initial_y, player_name)

    // Now, initialize properties specific to the Player class.
    this.score = initial_score or 0
    this.color = player_color or "blue"
    system.log("LOG: " + this.name + " (Player) created with score: " + this.score + ", color: " + this.color)
  end

  // Player-specific method to add points to the score.
  addScore = function(amount)
    this.score = this.score + amount
    system.log("LOG: " + this.name + " gained " + amount + " points. Total score: " + this.score)
  end

  // Overriding the draw method from Entity to draw players differently
  draw = function()
    if this.is_active then
      screen.setColor(this.color)
      screen.fillCircle(this.x, this.y, 8) // A slightly larger, colored circle for the player
      screen.setColor("white")
      screen.drawText(this.name + " (" + this.score + ")", this.x, this.y - 15, 8)
    end
  end
end

// Global variables to hold our game objects
local enemy = null
local hero = null
local item = null

// The init() function is called once when the program starts.
// This is where we typically create our initial game objects.
init = function()
  // Create instances (objects) from our classes
  enemy = new Entity(-50, 30, "Goblin_Grunt") // Calls Entity's constructor
  item = new Entity(40, -20, "Health_Potion") // Calls Entity's constructor

  hero = new Player(0, 0, "Brave_Knight", 50, "green") // Calls Player's constructor
end

// The update() function is called 60 times per second.
// This is where game logic and object interactions happen.
update = function()
  // Example: Move the enemy every second
  if system.time() % 1000 < 16 then // Roughly once per second (1000ms / 60fps)
    enemy.move(random.randInt(-5, 5), random.randInt(-5, 5))
  end

  // Example: Player input
  if keyboard.LEFT then hero.move(-1, 0) end
  if keyboard.RIGHT then hero.move(1, 0) end
  if keyboard.UP then hero.move(0, 1) end
  if keyboard.DOWN then hero.move(0, -1) end

  // Example: Player gains score if 'A' is pressed
  if keyboard.press.A then
    hero.addScore(10)
  end

  // Log current positions of our objects periodically
  if system.time() % 3000 < 16 then // Every 3 seconds
    system.log(enemy.getPositionString())
    system.log(hero.getPositionString())
  end
end

// The draw() function is called as fast as possible to render frames.
// This is where we tell our objects to draw themselves.
draw = function()
  screen.clear("darkgray") // Clear the screen each frame

  // Ask each object to draw itself
  enemy.draw()
  item.draw()
  hero.draw()

  // Display some info on screen
  screen.setColor("white")
  screen.drawText("Press arrow keys to move " + hero.name, 0, 90, 8, {align:"center"})
  screen.drawText("Press 'A' to increase " + hero.name + "'s score", 0, 80, 8, {align:"center"})
end

Summary of Key Concepts:

  • class ... end: Defines the blueprint.
  • constructor = function(...) ... end: Special method for initializing object properties when a new object is created.
  • new ClassName(args): How you create a new object (instance) from a class, which automatically calls its constructor.
  • this: Inside a class method or constructor, this refers to the specific object the method is being called on (or the object being created by the constructor).
  • extends ParentClass: Makes your new class inherit properties and methods from ParentClass.
  • super(args): In a child class's constructor, this calls the parent class's constructor to ensure parent properties are initialized.
  • Methods: Functions defined within a class. They operate on the object's this properties.

Classes are the foundation of Object-Oriented Programming (OOP) and will make your microScript projects much more organized, scalable, and maintainable.

I hope this helps you!

This helps a lot thank you! But if I had a function inside the class that required me to use inputs from the keyboard, would I have to call upon those functions in the update function?

Yes, absolutely! :)

You've hit on a very important concept in game development: the game loop!.

In microStudio (and most game engines), the update() function is your primary game logic handler (duh). It's called repeatedly (typically 60 times per second, I hope you knew that) and is where all game state changes, physics calculations, animations, and crucially, input processing should happen.

Here's why, and how you'd structure it:

Why Input Goes in update()

  1. Frame-by-Frame State: Keyboard and other inputs (keyboard.LEFT, mouse.pressed, touch.touching) reflect their state for the current frame. If you don't check them in update(), you'll miss the input entirely for that frame.
  2. Continuous Action: Many game actions, like moving a character, require continuous checks. Holding down an arrow key means "move left every frame while held down."
  3. Synchronization: By processing input in update(), your character's movement or other actions are perfectly synchronized with the rest of your game's logic and physics updates.

How to Structure It: Calling Class Methods from update()

You'll create methods within your class that handle input, and then you'll call these methods from your main update() function.

Let's enhance our Player class to have its own handleInput method like this:

// (Previous Entity class definition here, unchanged)

// This is our derived class: Player
Player = class extends Entity
  score = 0
  color = "blue"
  speed = 2 // Player-specific speed

  constructor = function(initial_x, initial_y, player_name, initial_score, player_color)
    super(initial_x, initial_y, player_name)
    this.score = initial_score or 0
    this.color = player_color or "blue"
    system.log("LOG: " + this.name + " (Player) created with score: " + this.score + ", color: " + this.color)
  end

  addScore = function(amount)
    this.score = this.score + amount
    system.log("LOG: " + this.name + " gained " + amount + " points. Total score: " + this.score)
  end

  // NEW METHOD: Handles player input
  handleInput = function()
    local dx = 0
    local dy = 0

    if keyboard.LEFT then
      dx = dx - this.speed
    end
    if keyboard.RIGHT then
      dx = dx + this.speed
    end
    if keyboard.UP then
      dy = dy + this.speed
    end
    if keyboard.DOWN then
      dy = dy - this.speed
    end

    // Call the base 'move' method if there's any movement
    if dx != 0 or dy != 0 then
      this.move(dx, dy)
    end

    // Check for other actions, like firing a weapon or interacting
    if keyboard.press.SPACE then // 'press' checks if the key was just pressed this frame
      system.log(this.name + " used an action!")
      // You could call a 'fireWeapon()' or 'interact()' method here
    end

    if keyboard.press.A then
      this.addScore(10)
    end
  end

  // Overriding the draw method from Entity to draw players differently
  draw = function()
    if this.is_active then
      screen.setColor(this.color)
      screen.fillCircle(this.x, this.y, 8) // A slightly larger, colored circle for the player
      screen.setColor("white")
      screen.drawText(this.name + " (" + this.score + ")", this.x, this.y - 15, 8)
    end
  end
end

// Global variables to hold our game objects
local enemy = null
local hero = null
local item = null

init = function()
  enemy = new Entity(-50, 30, "Goblin_Grunt")
  item = new Entity(40, -20, "Health_Potion")
  hero = new Player(0, 0, "Brave_Knight", 50, "green")
end

update = function()
  // Call the player's handleInput method every frame
  hero.handleInput()

  // Example: Move the enemy every second
  if system.time() % 1000 < 16 then // Roughly once per second (1000ms / 60fps)
    enemy.move(random.randInt(-5, 5), random.randInt(-5, 5))
  end

  // Log current positions of our objects periodically
  if system.time() % 3000 < 16 then // Every 3 seconds
    system.log(enemy.getPositionString())
    system.log(hero.getPositionString())
  end
end

draw = function()
  screen.clear("darkgray")

  enemy.draw()
  item.draw()
  hero.draw()

  screen.setColor("white")
  screen.drawText("Press arrow keys to move " + hero.name, 0, 90, 8, {align:"center"})
  screen.drawText("Press SPACE for action", 0, 80, 8, {align:"center"})
  screen.drawText("Press 'A' to increase " + hero.name + "'s score", 0, 70, 8, {align:"center"})
end

Key Takeaways from the Example:

  1. Encapsulation: The Player class now "knows" how to handle its own input. The main update() function doesn't need to know the specifics of how a player moves; it just tells the hero object, "Hey, handleInput()!"
  2. handleInput() Method: This new method inside the Player class checks for keyboard states (keyboard.LEFT, keyboard.press.SPACE, etc.) and then calls other methods (this.move(), this.addScore()) or directly modifies this.x, this.y.
  3. Main update() simplified: The main update() function now only has a single line hero.handleInput() for player input, keeping it clean and readable. This becomes even more powerful when you have many objects.

This pattern is extremely common and powerful in game development. As your game grows, you might have a list of all game objects, and your update() function might look like this:

// Assuming 'game_objects' is a list of all active entities (players, enemies, items)
update = function()
  for obj in game_objects
    // If the object has an 'update' method, call it.
    // This allows each object to manage its own logic, including input, AI, movement, etc.
    if obj.update then
      obj.update()
    end
  end
end

Then, each Entity (and Player by extension) could have its own update() method:

Entity = class
  // ... existing properties and methods ...

  update = function()
    // Default entity update logic (e.g., gravity, basic movement, checking for 'delete' flag)
    // For now, let's just log something.
    // system.log(this.name + " is updating!")
  end
end

Player = class extends Entity
  // ... existing properties and methods ...

  // Player's update method, which overrides the Entity's update
  update = function()
    super.update() // Call the parent Entity's update method first (if needed)

    this.handleInput() // Player-specific input handling
    // ... other player-specific update logic ...
  end
end

This way, your main update() function acts as an orchestrator, telling all your game objects to update themselves, and each object internally decides how it should update (including handling its own input if it's player-controlled).

This structure makes your code much more modular, easier to debug, and scalable for larger games.

I hope that made sense! :)

Post a reply

Progress

Status

Preview
Cancel
Post
Validate your e-mail address to participate in the community