Jump to content

6 Reasons You Should Use Ultra Game System


reepblue

1,518 views

 Share

Back in July, I set out to create a base for creating a version of Cyclone with Ultra Engine. I've gotten a lot of feedback from the release and started to conceptualize ideas on how to prevent or limit on making the same mistakes.

One major goal I had was to compartmentalize the system. This should just exist on it's own and game code should just lay on top of this. This was possible thanks to the event system which cuts down on the number of pointers that need to be passed. Components can just listen to events from the program and act accordingly. 

Lastly, I've made the decision to open source this on GitHub as this is the kind of thing that users think about when it's too late. People just want to work on their cool games and not worry about the window or if a setting will be applied correctly. 

So here are 6 reasons/features of the Ultra Game System! 

1. Window Management

One of the top complaints I got with Cyclone is that users could not resize the window in-game. This was an engine limitation of Leadwerks This is now possible in Ultra Engine but it needs some elbow grease to make it work. The Game System does this for you. All you need to do is call GetProgram()->ResizeApp() and the graphics window class will recreate the window and framebuffer for you. By default, pressing F11 will swap between windowed mode and full screen. 

2. Out-Of-Game Settings

The Game System has an isolated window application for changing settings. The idea behind this is so there's a way to modify any window or graphics setting if an in-game one is unavailable. You probably shouldn't ship with just this solution as many people prefer to edit any setting within the game itself. 

This is accessible by using the -settings flag with the program.

3. Action Based Input System

Years of research into this paid off when the Input Update for Cyclone released. The knowledge was carried over for the Game System. Only this time it's more dynamic! 

Simply set the controls in your main.cpp file:

// Define default controls.
static void InstallControls(shared_ptr<GameController> controller)
{
    if (controller == NULL) return;

    // Program actions
    controller->SetAction("Pause", BUTTON_KEY_ESCAPE);
    controller->SetAction("Terminate", BUTTON_KEY_END);
    controller->SetAction("ConsoleApp", BUTTON_KEY_F1);
    controller->SetAction("Fullscreen", BUTTON_KEY_F11);
    controller->SetAction("Screenshot", BUTTON_KEY_F2);
    controller->SetAction("Quick Save", BUTTON_KEY_F5);
    controller->SetAction("Quick Load", BUTTON_KEY_F6);

    // Camera
    ButtonAxis moveaxis =
    {
        BUTTON_KEY_W,
        BUTTON_KEY_S,
        BUTTON_KEY_A,
        BUTTON_KEY_D
    };
    controller->SetAction("Movement", moveaxis, "InGameControls");
    controller->SetAction("Camera", AXIS_MOUSE, "InGameControls");

    controller->SetAction("Sprint", BUTTON_KEY_SHIFT, "InGameControls");
    controller->SetAction("Crouch", BUTTON_KEY_CONTROL, "InGameControls");
    controller->SetAction("Climb", BUTTON_KEY_Q, "InGameControls");
    controller->SetAction("Desent", BUTTON_KEY_E, "InGameControls");
    controller->SetAction("Jump", BUTTON_KEY_SPACE, "InGameControls");

    // Settings
    controller->SetSetting("Raw Mouse", false);
    controller->SetSetting("Inverse Mouse", false);
    controller->SetSetting("Mouse Smoothing", 0.0f);
    controller->SetSetting("Mouse Look Speed", 1.0f);
}

Then deriving your components off of the GameObject class, you can use GetInput() for input functionality. 

    virtual void UpdateInput()
    {
        // Movement
        if (allowmovement)
        {
            float speed = movespeed / 60.0f;
            if (GetInput()->Down("Sprint"))
            {
                speed *= 10.0f;
            }
            else if (GetInput()->Down("Crouch"))
            {
                speed *= 0.25f;
            }

            if (GetInput()->Down("Climb")) GetEntity()->Translate(0, speed, 0);
            if (GetInput()->Down("Desent")) GetEntity()->Translate(0, -speed, 0);
            auto axis = GetInput()->Axis("Movement");
            GetEntity()->Move(axis.x * speed, 0, axis.y * speed);
        }
    }

Best part is the "Controls" tab will reflect whatever you have defined!

image.png.97224a199bfb1b68b6e6fb742527394d.png

4. User Input via Console

Having a developer console is essential for developing any game! The Game System has a very simple but flexible console that doesn't need any commands registered beforehand. To define a new command, just poll the EVENT_CONSOLEEXECUTE id in your component's ProcessEvent function.

virtual void Start()
{
	Listen(EVENT_CONSOLEEXECUTE, GetProgram());
}

virtual bool ProcessEvent(const Event& e)
{
	if (e.id == EVENT_CONSOLEEXECUTE)
	{
		auto line = e.text.Split(" ");
		auto cmd = line[0].ToString();
		if (line.size() > 1 && !line[1].empty())
		{
			if (cmd == "crosshair")
			{
				bool hide = (bool)line[1].ToInt();
				hudcamera->SetHidden(!hide);
				Print(QuoteString(cmd) + " has been set to: " + line[1]);
			}
		}
	}
}

5. Sound Managment

A layer of functionality in the Game System allows for cleaner sound origination and playback. You can store sound variables (files, volume, pitch, range, etc) within a JSON script.

{
	"audioProfile":
	{
		"file": "Sound/radio_dreamlandloop_mono.wav",
		"volume": 0.5,
		"range": 35.0,
		"loop": true
	}
}

Then load the file with the GameSpeaker class.

auto file = "Sound/Profiles/Radio.json";
shared_ptr<GameSpeaker> speaker = CreateGameSpeaker(file, GetEntity()->GetPosition());

The GameSpeaker has Save/Load functions so the speaker's time and state can be restored. Here's an example of creating and restoring a GameSpeaker.

virtual bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags)
{
    Print("Loading component " + QuoteWString(name));
    if (speaker)
    {
         if (!properties["componentspeaker"].is_null()) speaker->Load(properties["componentspeaker"], binstream, scene, flags);
    }
    else
    {
        auto file = "Sound/Profiles/Radio.json";
        speaker = CreateGameSpeaker(file, GetEntity()->GetPosition());
        if (!properties["componentspeaker"].is_null()) speaker->Load(properties["componentspeaker"], binstream, scene, flags);
    }
     return true;
}

virtual bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags)
{
    properties["componentspeaker"] = {};
    if (speaker) speaker->Save(properties["componentspeaker"], binstream, scene, flags);
    return true;
}

As of right now, the sound system doesn't support audio filters as I feel that the filter should be applied within a volume (or to the listener) and not the sound/speaker itself. I'm still thinking about how that should work. 

6. Better Render Layer Managment

Intergrated today, The Canvas class is a great way to ensure that there is one camera per render layer. I ran into an issue where a component would draw 2D graphics to the framebuffer, but when there were multiple instances of them, multiple cameras were being made and drawing over each other. Using GetCanvas() can prevent this from happening.

auto canvas = GetCanvas(world, RENDERLAYER_HUD);
auto sprite = CreateSprite(world, 1.0f, 1.0f);
sprite->SetPosition((float)sz.x / 2, (float)sz.y / 2);
RenderToCanvas(sprite, canvas);

The Game System will be my main focus until the Ultra Engine version of Cyclone starts development which will use this as a foundation.  I'll also be working on other small things to share so be on the lookout for those! 

  • Like 6
  • Upvote 2
 Share

5 Comments


Recommended Comments

It's now easier to get started! 

The system is installable as a project template and component registration is much more streamlined. 

Link to comment
1 hour ago, Alienhead said:

Do you have any plans on releasing a LUA version?

The code will remain C++ but in the future I plan to expose the more useful classes to Lua. The idea is that you can still have your game component code as Lua while the backend stuff is C++. 

I'm not sure how to manage/distribute something like this as I would be required to build the executables for all platforms on every release. 

  • Like 1
Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...