A Standard Game from which most games should derive?

This is something that's been mentioned a few times and I've been thinking about recently so I thought I'd get some community feedback before spending any time working on it.

I voted for the not plausible option, although I think that selection is more strongly worded than I really feel.  In my opinion, if you are putting together a production quality game, you should really put together the foundation yourself to really know what it's doing.  On top of that, the last few games I built that were not on top of SimpleGame classes were all different.  We could write something generic that covered all bases, but I believe it could become more bloated than I personally would want to use. 



My 2 cents.

My assumption was that we could manage some sort of configuration that would define standard aspects and could be overridden for deeper functionality.



However, this is why I wanted to take a poll.  I could be way off base expecting that the majority of games utilize mostly similar core functionality.

I think it would be worth fleshing out some more back end stuff for game development. Configuration and resource management, scripting, in game console etc. But I wouldn't limit it to code for the final game only, I really think that we need more tools for jME. The best thing would be to program them as plugins/features for an Eclipse RCP project.

I also voted yes, even though I have to admit, that every game is different indeed.

Still it would be helpfull to have a complete example where you can learn from, even if it has some completely different gameplay, than your own.

Better having a complete example than having none.

nice to learn from for noob to intermediate probably, but in my opinion not plausible for serious productions where lot of "simplegame" type of stuff is up to the leveldesigner to produce in your editor. everything from cams to inputhandlers to renderpasses to rootnodes, all read in from disk/configfiles/net etc etc…hard to make generic(different genres need different kind of data at different times and so on)

i went with option 2. it might help, but i doubt the overall need for it. there are already lot's of different basic gametypes in jme and it's relatively easy to use one of those or build your own with one of them as a base. so the effort might as well spend elsewhere.

I voted Yes, … but i'd see this more as an example game with all the aspects a prod quality game could need in place.

So perhaps more the "if i don't need it i'll strip it out" approach.



Suggested Features:



FixedLogicrate approach

GameStates

RenderPasses

JmePhysics2



…  :slight_smile:

And my question is… how would you get something different from SimpleGame, other than maybe using a fixed rate timer instead of full boar.



What I think would be better, is to have SimpleManager's instead, easy ways to load/unload models, much like the TextureManager (I remember when that wasn't part of jME), maybe something for lights, I haven't really messed with having a lot of lights in jME or even spent time reading on it so I don't know how important that will be. Of course, these things wouldn't be so much a part of a better SimpleProductionGame class, as they'd just be handy to have in general. There is a small set of things that will exist in pretty much every game… models, lights, textures, controls, some kind of menu's, etc. I a simplified way to manage those things would be benificial. In my own game I try to reduce everything to the point of getModel("name") or getStaticModle("name"), and only load it as necessary (similar to the texturemanager that already exists).

There are several points I want to address in a solution of this nature:


  • Management of GameStates

  • Handling of Client/Server modes (so you can develop one Game and set it to be client or server)

  • Fixed LogicRate/FrameRate/etc configurable so the user can define in their configuration how they want it to work

  • Some general settings that the user can define about level of detail that could be abstracted and utilized by models in the scene to determine how high resolution they should display and what textures to use

  • Shadow support configurable

  • Debug mode that can show a floating JFrame with information about the game and ability to pause the update process


...and much more.

I think the abstraction can primarily be accomplished through GameStates as there wouldn't really be anything being "done" in this Game but rather all the work would be done in the gamestates instead.  This also falls in line with all the changes I'm intending to make to GameStates, but I'll post more about that soon.

It seems like once you decide to start managing client server information and other stuff outside the scope of jME's core, you are really talking about making another layer on top of jME, as opposed to a base class someone would extend for their game… something like monkeyworld3d.



On the bright side, it seems like almost all of the stuff exists in one place or another for someone to build a commercial game design engine on jME (similar to 3d Game Studio or something like that).



I'm probably not fully understanding what you are saying, but either way it seems it'd have to be very large scale to be used as a commercial game base, or very small scale (basic setup of physics and other stuff along with maybe a few helper functions to make things go etc). Managing network communication in an efficient flexible way seems like a lot to add to a simplegame type class on it's own.



I think better than trying to make a new base game suitable for most commercial apps, you'd be better off to create a tutorial that shows how to build a game including those features which for the most part already exist in seperate packages in jmex. Maybe you already have all the issues figured out and know how to do it but it strikes me as ether very simple and would be largely ignored like simplegame in favor of a more customized approach, or very large scale, to the point of really being another layer of abstraction on top of jME and not what someone working with jME would typically build a game off of (like typically people using jME don't directly access lwjgl).

I'm not talking about including functionality for networking and such into this, but rather providing a mechanism to bring it to fruition.  The actual code for this would like not be much greater than SimpleGame.  For the Client/Server aspect it will primarily consist of an instantiation "mode" that determines if it will be in Client mode or Server mode.  Server mode is identical to Client mode except it ignores all initialization of graphical and sound elements.  In addition it would use DummyDisplaySystem instead of the LWJGLDisplaySyste, use NanoTimer instead of LWJGLTimer, etc.



It just seems logical to have one Game that I can work with being able to abstract away from it being client or server.  This way I'm not having to duplicate a lot of the same work to create the two when one is sufficient.

That makes sense… the way I do my networking, is I seperate things into client, server, and shared. The data objects all go into shared, unless they are exclusive to the server (provide more information than I want public to the players) or the client (used for a visual effect). I also place all kinds of other things there, static variables that represent different commands in my network code, and a pile of utility functions and of course, the actual game logic goes into shared. By doing it this way, there is very little duplication, I can generally write something once and it's good in both places. The shared package generally handles the manipulation of objects etc.



I am not however, using jME in my server, I designed the game to work off of a 2D tile basis… and while I intend to include more 3d elements to the game itself, generally only the display side of things cares about that at all. That's made things very simple for me in a lot of ways but I don't think that'd work for a lot of games.

Hi Darkfroggie :), my 2 cents:



It might be helpful, and probably help a lot to start doing things using several jme features without much trouble. But if we're talking about 'real game production', on my experience you always end up doing 'a new game core' which suits better your purposes.



If it's modular, and let's you automate the use and management of many of the features on jme, and your 'own' implementations on several bits, could be interesting. But I guess that achieving that is really difficult, as you have to develop a complex system for that.



I find the current 'game' classes as a good starting point to develop your own 'game core', and work as you feel more comfortable. It's an 'extra work' to start developing a game, but you do it once and for new games then you can improve or modify whatever you need. Also, knowing 'your core' helps a lot to finding bugs and so.



So, is interesting, but if your target is developing a system that would let you implement 'just the game' (and any sort of game) but letting you control 'easily' every jme feature without all the hassle of knowing the 'inners', and keeping the best performance along the way, that system would end up being really complex and hard to maintain.



On my experience, the 'best' approach I've found on game developing, is apply a particular solution to a particular problem. Trying to cover every possibility always end up in a mess (although that might be because I'm a crappy coder  ).



Anyway, if you feel like doing it, just do it (I sound like a commercial spot XD).

Again, I'm not trying to come up with the end-all-be-all solution here.  My purpose is more to push the complexity out of the Game and into the GameStates really.  This simplifies the game itself and even reduces the complication per aspect.



Could someone explain some aspects that are so different from game to game?  People keep mentioning it but I don't really know of any good examples…

Hi again,



Yes, I think I didn't fully understand what you are up to. Let's see the points you talk about:


  • Management of GameStates: Not sure what you mean, there's a GameStateManager already. I guess you want to automate the creation/disposal of GameStates.
  • Handling of Client/Server modes (so you can develop one Game and set it to be client or server): I guess you want to 'wrap' the networking functionality, so the game does not needs to know on which mode is running to operate.
  • Fixed LogicRate/FrameRate/etc configurable so the user can define in their configuration how they want it to work: There are several 'game' classes with a kind of approach each. I guess you want to 'automate' the process by creating the required instance, or have all the implementations centralized but just call the 'required update method' based on what the user wanted.
  • Some general settings that the user can define about level of detail that could be abstracted and utilized by models in the scene to determine how high resolution they should display and what textures to use: This sounds like an automation for lod, maybe wrapping the functionality and providing sets of models/textures as the configuration.
  • Shadow support configurable: also automating the current shadow support, easily enabling/disabling it and selecting which kind of shadows you want to use.
  • Debug mode that can show a floating JFrame with information about the game and ability to pause the update process: That's evident :wink:



    And you say that functionality is done on the game states, so I guess you want each 'gamestate' be able to setup a configuration on its own.



    Yes, it seems its a time saver when you want to do those sort of things, and the game state system is ok for you (which might be in most cases, is well designed ;)).



    That's is more affordable, and yes, I think is much better than having a lot of 'game' classes around. You were right :slight_smile:



    But on the question about differences between games… Just a sample: If I just want do a 2D pacman, I don't need most of that functionality, but it will be there, just being 'unused', maybe eating up resources and haunting my code when is midnight, howling 'uuuuuse meeeeee…' :stuck_out_tongue_winking_eye:



    Not a big issue, as most games would be 3D and sure they would take advantage of that system. But I feel it would be 'incomplete' for real game production anyway, as there are things as a resource manager, a messaging system, i18n support, scripting, etc… which aren't yet 'part' of the jme. And those are usually part of the core, and when you're using a core without that funcionality, you end up 'modifying' the core… And we're back to the beginning :wink:



    For example, I don't want to care about 'when' I should call the display to change it's resolution. I just want to say 'change the resolution' and expect it does when is convenient. A messaging system would solve this. And also when this does happen, I don't want to track every visual-related thing I created to match the resolution change, I want them to do any adjustments automatically.



    Or if I want to load a png file, I don't want to keep an eye if it's on the classpath, or outside it. I want a resource manager to look for that file wherever it can be. And if it is a file with some sentence in english drawed on it, and I want the system to load the 'french' version of that file, I want the resource manager to talk with the i18n manager, so if the user did set up the game in french, what the system will load is the french version of the file.



    And so on… Those kind of bits are what I miss for 'real game production'. You want to do 'the game', not the 'system'. If you have to do the 'system' or care too much about what the system does, you are not still on the 'real game production' point.



    I know, I'm a lazy and crappy coder  :smiley: But what I like is do games, I hate programming  :smiley:

Yeah, I think I'm perpetually misunderstood on this topic and rather than continue trying to explain, when I find some time I'll go ahead and write a basic implementation of what I'm thinking of and then get some feedback on that instead of a discussion of vapor. :wink:

Okay, so I went ahead and did a quick implementation of what I've been thinking about.  I think there's a lot of room for adjustment here, but this is the basic concept.  The majority of issues have been pushed off into the GameStates for any actual work to be done:


/*
 * Copyright (c) 2003-2006 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.app;

import java.util.logging.*;
import java.util.prefs.*;

import com.jme.image.*;
import com.jme.input.*;
import com.jme.math.*;
import com.jme.renderer.*;
import com.jme.scene.*;
import com.jme.scene.state.*;
import com.jme.system.*;
import com.jme.util.*;
import com.jmex.model.XMLparser.Converters.*;
import com.jmex.sound.openAL.*;

/**
 * <code>StandardGame</code> intends to be a basic implementation of a game that can be
 * utilized in games as a logical next step from <code>SimpleGame</code> and can be utilized
 * in production games.
 *
 * @author Matthew D. Hicks
 */
public class StandardGame extends AbstractGame implements Runnable {
    private static String FONT_LOCATION = "/com/jme/app/defaultfont.tga";
   
    private static enum GameSetting {
        GAME_RENDERER,
        GAME_WIDTH,
        GAME_HEIGHT,
        GAME_DEPTH,
        GAME_FREQUENCY,
        GAME_FULLSCREEN,
        GAME_DEPTH_BITS,
        GAME_ALPHA_BITS,
        GAME_STENCIL_BITS,
        GAME_SAMPLES,
        GAME_MUSIC,
        GAME_SFX
    }
   
    private static final String DEFAULT_RENDERER = PropertiesIO.DEFAULT_RENDERER;
    private static final int DEFAULT_WIDTH = PropertiesIO.DEFAULT_WIDTH;
    private static final int DEFAULT_HEIGHT = PropertiesIO.DEFAULT_HEIGHT;
    private static final int DEFAULT_DEPTH = PropertiesIO.DEFAULT_DEPTH;
    private static final int DEFAULT_FREQUENCY = PropertiesIO.DEFAULT_FREQ;
    private static final boolean DEFAULT_FULLSCREEN = false; //PropertiesIO.DEFAULT_FULLSCREEN;
    private static final int DEFAULT_DEPTH_BITS = 8;
    private static final int DEFAULT_ALPHA_BITS = 0;
    private static final int DEFAULT_STENCIL_BITS = 0;
    private static final int DEFAULT_SAMPLES = 0;
    private static final boolean DEFAULT_MUSIC = true;
    private static final boolean DEFAULT_SFX = true;
   
    public static enum GameType {
        GRAPHICAL,
        HEADLESS
    }
   
    private Thread gameThread;
    private String gameName;
    private GameType type;
    private Preferences settings;
    private boolean started;
   
    private Text fps;
    private Node fpsNode;
    private Timer timer;
    private Camera camera;
    private ColorRGBA backgroundColor;
   
    public StandardGame(String gameName, GameType type, Preferences settings) {
        this.gameName = gameName;
        this.type = type;
        this.settings = settings;
        backgroundColor = ColorRGBA.black;
    }

    public void start() {
        // Validate settings
        if (settings == null) {
            settings = Preferences.userRoot().node(gameName);
        }
       
        gameThread = new Thread(this);
        gameThread.start();
    }
   
    public void run() {
        initSystem();
        assertDisplayCreated();
        initGame();
        if (type == GameType.GRAPHICAL) {
            timer = Timer.getTimer();
        } else if (type == GameType.HEADLESS) {
            timer = new NanoTimer();
        }
       
        // Main game loop
        try {
            float tpf;
            started = true;
            while ((!finished) && (!display.isClosing())) {
                timer.update();
                tpf = timer.getTimePerFrame();
               
                if (type == GameType.GRAPHICAL) {
                    InputSystem.update();
                }
                update(tpf);
                render(tpf);
                display.getRenderer().displayBackBuffer();
                Thread.yield();
            }
            started = false;
        } catch(Throwable t) {
            LoggingSystem.getLogger().log(Level.SEVERE, "Main game loop broken by uncaught exception", t);
        }
        cleanup();
        quit();
    }

    protected void initSystem() {
        if (type == GameType.GRAPHICAL) {
            display = DisplaySystem.getDisplaySystem(settings.get(GameSetting.GAME_RENDERER.toString(), DEFAULT_RENDERER));
            displayMins();
            display.createWindow(settings.getInt(GameSetting.GAME_WIDTH.toString(), DEFAULT_WIDTH),
                                 settings.getInt(GameSetting.GAME_HEIGHT.toString(), DEFAULT_HEIGHT),
                                 settings.getInt(GameSetting.GAME_DEPTH.toString(), DEFAULT_DEPTH),
                                 settings.getInt(GameSetting.GAME_FREQUENCY.toString(), DEFAULT_FREQUENCY),
                                 settings.getBoolean(GameSetting.GAME_FULLSCREEN.toString(), DEFAULT_FULLSCREEN));
            camera = display.getRenderer().createCamera(display.getWidth(), display.getHeight());
            display.getRenderer().setBackgroundColor(backgroundColor);
           
            // Configure Camera
            cameraPerspective();
            cameraFrame();
            camera.update();
            display.getRenderer().setCamera(camera);
           
            display.setTitle(gameName);
           
            if ((settings.getBoolean(GameSetting.GAME_MUSIC.toString(), DEFAULT_MUSIC)) || (settings.getBoolean(GameSetting.GAME_SFX.toString(), DEFAULT_SFX))) {
                SoundSystem.init(camera, SoundSystem.OUTPUT_DEFAULT);
            }
        } else {
            display = new DummyDisplaySystem();
        }
    }
   
    private void displayMins() {
        display.setMinDepthBits(settings.getInt(GameSetting.GAME_DEPTH_BITS.toString(), DEFAULT_DEPTH_BITS));
        display.setMinStencilBits(settings.getInt(GameSetting.GAME_STENCIL_BITS.toString(), DEFAULT_STENCIL_BITS));
        display.setMinAlphaBits(settings.getInt(GameSetting.GAME_ALPHA_BITS.toString(), DEFAULT_ALPHA_BITS));
        display.setMinSamples(settings.getInt(GameSetting.GAME_SAMPLES.toString(), DEFAULT_SAMPLES));
    }
   
    private void cameraPerspective() {
        camera.setFrustumPerspective(45.0f, (float)display.getWidth() / (float)display.getHeight(), 1.0f, 1000.0f);
        camera.setParallelProjection(false);
        camera.update();
    }
   
    private void cameraFrame() {
        Vector3f loc = new Vector3f(0.0f, 0.0f, 25.0f);
        Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
        Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
        Vector3f dir = new Vector3f(0.0f, 0.0f, -1.0f);
        camera.setFrame(loc, left, up, dir);
    }
   
    protected void initGame() {
        if (type == GameType.GRAPHICAL) {
            // Frames Per Second stuff
            AlphaState as = display.getRenderer().createAlphaState();
            as.setBlendEnabled(true);
            as.setSrcFunction(AlphaState.SB_SRC_ALPHA);
            as.setDstFunction(AlphaState.DB_ONE);
            as.setTestEnabled(true);
            as.setTestFunction(AlphaState.TF_GREATER);
            as.setEnabled(true);
            TextureState font = display.getRenderer().createTextureState();
            font.setTexture(
                    TextureManager.loadTexture(StandardGame.class.getResource(FONT_LOCATION),
                    Texture.MM_LINEAR,
                    Texture.FM_LINEAR));
            font.setEnabled(true);
            fps = new Text("FPS label", "");
            fps.setTextureCombineMode(TextureState.REPLACE);
            fpsNode = new Node("FPS node");
            fpsNode.attachChild(fps);
            fpsNode.setRenderState(font);
            fpsNode.setRenderState(as);
            fpsNode.updateGeometricState(0.0f, true);
            fpsNode.updateRenderState();
        }
       
        // Create the GameStateManager
        GameStateManager.create();
    }
   
    protected void update(float interpolation) {
        // Update the GameStates
        GameStateManager.getInstance().update(interpolation);
       
        if (type == GameType.GRAPHICAL) {
            fps.print(Math.round(timer.getFrameRate()) + " fps");
            if ((settings.getBoolean(GameSetting.GAME_MUSIC.toString(), DEFAULT_MUSIC)) || (settings.getBoolean(GameSetting.GAME_SFX.toString(), DEFAULT_SFX))) {
                SoundSystem.update(interpolation);
            }
        }
    }
   
    protected void render(float interpolation) {
        display.getRenderer().clearBuffers();
        GameStateManager.getInstance().render(interpolation);
        display.getRenderer().draw(fpsNode);
    }
   
    protected void reinit() {
        displayMins();
        display.recreateWindow(settings.getInt(GameSetting.GAME_WIDTH.toString(), DEFAULT_WIDTH),
                               settings.getInt(GameSetting.GAME_HEIGHT.toString(), DEFAULT_HEIGHT),
                               settings.getInt(GameSetting.GAME_DEPTH.toString(), DEFAULT_DEPTH),
                               settings.getInt(GameSetting.GAME_FREQUENCY.toString(), DEFAULT_FREQUENCY),
                               settings.getBoolean(GameSetting.GAME_FULLSCREEN.toString(), DEFAULT_FULLSCREEN));
    }
   
    protected void cleanup() {
        GameStateManager.getInstance().cleanup();
    }
   
    protected void quit() {
        if (display != null) {
            display.reset();
            display.close();
        }
    }
    /**
     * Override the background color defined for this game. The reinit() method
     * must be invoked if the game is currently running before this will take effect.
     *
     * @param backgroundColor
     */
    public void setBackgroundColor(ColorRGBA backgroundColor) {
        this.backgroundColor = backgroundColor;
    }
   
    /**
     * Gracefully shutdown the main game loop thread. This is a synonym
     * for the finish() method but just sounds better.
     *
     * @see #finish()
     */
    public void shutdown() {
        finish();
    }
   
    /**
     * Will return true if within the main game loop. This is particularly
     * useful to determine if the game has finished the initialization but
     * will also return false if the game has been terminated.
     *
     * @return
     *      boolean
     */
    public boolean isStarted() {
        return started;
    }
}



Criticism is welcome.  This is a first iteration and only took me like 20 minutes to write, so I'm not really emotionally tied to it. ;)

Just realized that this version of the class doesn't have any changes to jME core, so I went ahead and checked it into the repository.  If everyone hates it you can either burn me then remove it from CVS or just remove it…personally I would prefer that latter, but ya know, whatever. :wink:

I forgot about fixed-framerate support so just got done writing that feature into StandardGame.  It defaults to not using fixed-framerate, but it's configurable via the settings, so implementing games can give the option to the player to set fixed-framerate, framerate sync'd to refresh rate, or "as fast as possible". :wink:



However, I duplicated the majority of the code from FixedFramerateGame but it seems there's a mathmatic flaw in the construction because it seems to show the FPS a few frames below the desires FPS and as you go higher the level of error is off.  It's possible it's something I'm doing, but I pretty much just duplicated the code.