RTS update #2: Classes

5
Official Construct Team Post
Ashley's avatar
Ashley
  • 3 Oct, 2022
  • 1,450 words
  • ~6-10 mins
  • 1,646 visits
  • 3 favourites

Since the last blog I've added some very basic interactivity. You can click to select units, and click somewhere else to move the selected units there. They just move directly there in a straight line at the moment, but everything like pathfinding, crowd control and so on can come later down the line. I could easily spend a few weeks just refining unit movement, but for this project I want to get some kind of multiplayer interactivity as soon as possible and work up from there, so in the mean time I'm essentially just putting placeholder functionality in for everything else.

In this post I'll go over the class structure and some of the interesting points about the code so far.

Class structure

GameServer will run the main logic of the game, running in its own WebWorker. There are various base classes (under classes/) and unit classes (under units/) that it uses.

Base classes

The base classes are fairly generic and will be used for all sorts of things. I'm not totally confident in the class structure at this early stage, but for now what I'm aiming for is:

  • PositionedObject is something static that just has a position, e.g. the static platform of a defensive turret.
  • PositionedAndAngledObject inherits from PositionedObject, and adds an angle. The turret of a static defensive unit is an example of something that has both a position and an angle. (The class name is a bit clunky, but I always find a longer and more descriptive name makes the code clearer. Actually typing makes up a surprisingly small amount of time spent coding, so a longer name that makes things clearer is worth it.)
  • MovableObject inherits from PositionedAndAngledObject, and adds a speed. Examples of things with a position, angle and speed include both bullets and the moving platform of a tank.

Unit classes

The class structure I'm aiming to get to for units is:

  • Unit represents any kind of unit. It will comprise of what I'm calling a "platform" - the bottom part of the unit, such as the driving part of a tank - and optionally also a turret (or possibly multiple turrets...) that rotates independently of the platform to aim at enemies and fire projectiles at them.
  • UnitPlatform is a base class to represent the bottom part of the unit. There's a MovableUnitPlatform derived class to represent something like the driving platform of a tank that can move. There'll be a StaticUnitPlatform class too, but I'll get to that later.
  • There will be a class for turrets too... and I'll get to that later, too. I'm focusing on the basics!

An interesting point about the class hierarchy is that MovableUnitPlatform seems to need multiple inheritance - it could derive from both UnitPlatform and MovableObject as it combines both. JavaScript doesn't directly support multiple inheritance, and many programmers think it is a dubious concept anyway, since it brings some difficult complications to the design of the programming language. So my approach is to avoid multiple inheritance and use composition instead. In short that means instead of inheriting from a class, it is added as a property. It's a fairly arbitrary choice, but I went with making MovableUnitPlatform derive from UnitPlatform, and making MovableObject a property. This has its own shortcomings, like needing to add extra methods to UnitPlatform to access MovableObject rather than automatically inheriting all its methods. You can see that happening with the GetPosition() method. But it's simple, and experienced programmers like simple things, and are wary of complicated things.

Private fields

I'm adopting a slightly different programming style for this project. Much of my work on Construct needs to be backwards-compatible with the oldest browsers it's feasible to support, to ensure everything works everywhere. Public and private class fields are a relatively new feature of JavaScript, and while they're too new to use in Construct, I've gone all-in with them for this project.

In the past JavaScript properties were always public - i.e. any code anywhere could read and write the value of any property on any object. This can make code disorganized and makes it difficult to enforce correct usage of classes. Coding conventions like "anything that starts with an underscore is meant to be private" can help, but it's still tempting to take shortcuts; it's better to properly enforce these rules. JavaScript now supports private class features, which essentially means putting # in front of the property or method name. Then only that class can access it and nothing outside the class is allowed to directly access it.

The code snippet below shows the syntax.

class MyClass {
	
	// A public field (much like a normal property)
	publicField;
	
	// A private field (only accessible by this class)
	#privateField;

	// A public method
	publicMethod() { /* ... */ }

	// A private method
	#privateMethod() { /* ... */ }
}

One interesting point to note is private properties must be defined as a field before being used. They can't be added in the class constructor like normal properties.

I'm going with private properties for pretty much all class properties, and only using public methods to access them. This makes sure the class methods are in control of how the properties are used. You can see examples of this in most classes in the codebase, such as PositionedObject.

Modules

I'm also going all-in with JavaScript Modules. They're a really nice way to organise code. Each script file is its own self-contained module. Anything else the module needs is imported at the top, and anything you want to share outside the module is exported.

I'm going with one class per file, and the file just exports that class, e.g.:

export class MyClass {

	// ... all the code for MyClass ...

}

That class can then be imported in to other files that want to use it, e.g.:

import { MyClass } from "./myClass.js";

You can see both of these in positionedAndAngledObject.js.

Miscellaneous

There's a similar class structure for units for GameClient under clientUnits/. This class structure isn't quite as fleshed out yet. I may need to go with exactly the same structure as on the server to keep things consistent, but I'm not sure yet; I'll try a simplified model but change it later if necessary.

MultiEventHandler is a simple class that makes it much easier to handle lots of events with addEventListener and removeEventListener. Take a look at the code comments which explain what it does.

The main classes GameServer and GameClient each risk becoming a huge, sprawling class that manages enormous parts of the game by themselves - sometimes called a God object. To mitigate this I'm splitting any substantial part of the game logic off in to its own class. The first part of this is SelectionManager, which is a separate class that solely handles selecting units on behalf of GameClient.

There's a fair bit more I could go in to about messaging and the binary format used to sync the game state (also currently in a really minimal form), but I think that will be its own future blog post.

Conclusion

The game class structure is beginning to shape up. I think this is roughly the right approach and hopefully will avoid the need for any major refactoring.

I know I'm probably the most biased person possible to say this, but I'll say it anyway: I'm really enjoying writing this JavaScript code! I think JavaScript is a really underrated language for game development. Its support for modules, classes, private properties and everything else is so far making for nice clean code that works well. Adding support for modules in Construct was tough, but I'm really glad we did it as it makes the code far more organised and is the way modern JavaScript is now written. I also found Construct's property auto-completion wasn't working correctly for private class features, so I fixed it! That's the kind of benefit Construct gets from dogfooding the scripting feature like this. I've also made changes to the Project Bar and Layout View while working on this project, so it's not just the coding features that benefit. At some point I'll probably write a whole blog post about the Construct improvements that I've made along the way.

As ever you can see all the code on GitHub and download the Construct project to try it out. There's much more to be done though and I'll be blogging plenty more about it!

Past blog posts

In case you missed them here are the past blogs about this projects:

Subscribe

Get emailed when there are new posts!

  • 4 Comments

  • Order by
Want to leave a comment? Login or Register an account!