1 /* 2 This is a template for creating games with PixelPerfectEngine. 3 Contains some basic setup, but needs further work to be fully featured besides of adding some game logic. 4 5 Feel free to modify this, or even create your own templates (you might want to modularize). Contact me, if you encounter any errors on the way. 6 */ 7 8 module templates.gameapp; 9 //Some commonly used stuff 10 import std.stdio; 11 import std.typecons : BitFlags; //This is a good way to create bitflags, so you don't need to keep track of a bunch of boolean values. 12 13 import bindbc.sdl; //As of now, this is needed to initialize the SDL API. Might be changed in the future 14 //Graphics related imports 15 import pixelperfectengine.graphics.outputscreen;//Needed to display the final graphics 16 import pixelperfectengine.graphics.raster; //Needed to display layers in order 17 import pixelperfectengine.graphics.layers; //Imports all layers and layer-related functionality 18 import pixelperfectengine.graphics.bitmap; //Imports bitmaps and all bitmap-related functionality 19 //The next two lines imports the collision detector 20 import pixelperfectengine.collision.common; 21 import pixelperfectengine.collision.objectcollision; 22 //Various system related imports 23 import pixelperfectengine.system.input; //Every game needs some sort of interaction capability, and thus here's the input 24 import pixelperfectengine.system.file; //Used to load bitmap and image files 25 import pixelperfectengine.system.config; //Needed for configuration files 26 27 import pixelperfectengine.system.rng; //64 bit LFSR random number generator 28 import pixelperfectengine.system.timer; //Low-precision timer for keeping time-based events relatively precise 29 //Audio-related imports 30 import pixelperfectengine.audio.base.handler; //Most of the basic stuff we will need 31 import pixelperfectengine.audio.base.modulebase;//Module interfaces to play back sound effects otherwise not tied to the sequence being played back 32 import pixelperfectengine.audio.base.config; //Audio configuration loader and parser 33 import pixelperfectengine.audio.base.midiseq; //MIDI sequencer 34 //Imports the engine's own map format. 35 import pixelperfectengine.map.mapformat; 36 //Other imports that might be important. Uncomment any you feel you'll need. 37 /* import pixelperfectengine.system.common; */ 38 39 ///Our main function, needed for the program to operate. 40 ///You can add `string[] args` if your either really need or really want. 41 int main() { 42 initialzeSDL(); //Initializes the SDL subsystem, so we will have input and graphics 43 try { //A try-catch block to handle any errors. A bit ugly, but can save us when there's issues with debug symbols, or an error happened outside of a D code 44 GameApp app = new GameApp(); 45 app.whereTheMagicHappens(); 46 } catch (Throwable e) { 47 writeln(e); 48 } 49 return 0; 50 } 51 ///I generally like to put most of the application logic into one class to keep track of globals, as well as targets to certain events. 52 public class GameApp : SystemEventListener, InputListener { 53 ///Defines various states of the game. 54 ///I have added some quite useful and very often used ones 55 enum StateFlags { 56 isRunning = 1<<0, 57 pause = 1<<1, 58 mainMenu = 1<<2, 59 } 60 ///Defines various states for the control. Mainly used to avoid effects from typematic and such. 61 ///These are the bare minimum requirements for a game. 62 enum ControlFlags { 63 up = 1<<0, 64 down = 1<<1, 65 left = 1<<2, 66 right = 1<<3, 67 } 68 ///Stores the currently loaded map file with all related data. 69 MapFormat mapSource; 70 ///To display our game's graphics 71 OutputScreen output; 72 ///To manage our layers and palette. 73 Raster rstr; 74 ///For input handling. 75 InputHandler ih; 76 ///Detects object collisions that were registered to it. 77 ObjectCollisionDetector ocd; 78 ///Contains various game state flags (is it running, is it paused, etc). 79 BitFlags!StateFlags stateFlags; 80 ///Contains various control state flags 81 BitFlags!ControlFlags controlFlags; 82 ///Contains the pointer to the textlayer. 83 ///Can be used for the menu, status bars, etc. 84 TileLayer textLayer; 85 ///Contains pointer to the game field, so we can easily interact with it. 86 SpriteLayer gameField; 87 ///Contains the random number generator and its state. 88 RandomNumberGenerator rng; 89 ConfigurationProfile cfg; 90 //Audio related stuff goes here. 91 //Note: some of the audio stuff is preliminary. It works, but cannot handle certain cases, such as sample rate mismatches, or device disconnection. 92 //Sequencer is untested as of now due to a lack of time and manpower. 93 AudioDeviceHandler adh; ///Handles audio devices and outputs. 94 ModuleManager modMan; ///Handles the modules and their output. 95 ModuleConfig modCfg; ///Loads and handles module configuration, including routing, patches, and samples. 96 SequencerM1 midiSeq;///MIDI sequencer for MIDI playback. 97 98 /// Initializes our application. 99 /// Put other things here if you need them. 100 this () { 101 stateFlags.isRunning = true; //Sets the state to running, so the main loop will stay running. 102 output = new OutputScreen("Your app name here", 424 * 4, 240 * 4); //Creates an output window with the display size of 1696x960. 103 rstr = new Raster(424,240,output,0);//Creates a raster with the size of 424x240. 104 output.setMainRaster(rstr); //Sets the main raster of the output screen. 105 106 ih = new InputHandler(); //Creates an instance of an InputHandler (should be only one) 107 ih.systemEventListener = this; //Sets the system event target to this instance 108 ih.inputListener = this; //Sets the input event target to this instance 109 110 ocd = new ObjectCollisionDetector(&onCollision, 0); //Creates an object collision detector 111 //Let's create our layer for statuses, etc 112 textLayer = new TileLayer(8,8, RenderingMode.AlphaBlend); //Creates a TileLayer with 8x8 tiles and alpha blending 113 textLayer.paletteOffset = 512; //Sets the palette offset to 512. You might want to change this to the value to the place where you loaded your GUI palette 114 textLayer.masterVal = 127; //Sets the master value for the alpha blending, making this layer semi-transparent initially. 115 116 cfg = new ConfigurationProfile(); //Creates and loads the configuration profile. 117 //Comment the next part out, if you're having too much trouble with audio working, since you still can add sound later on. 118 //audio related part begin 119 AudioDeviceHandler.initAudioDriver(OS_PREFERRED_DRIVER); //Initializes the driver 120 AudioSpecs as = AudioSpecs(predefinedFormats[PredefinedFormats.FP32], cfg.audioFrequency, 0, 2, cfg.audioBufferLen, 121 Duration.init); //Sets up a default audio specification 122 adh = new AudioDeviceHandler(as, cfg.audioBufferLen, cfg.audioBufferLen / cfg.audioFrameLen); //Creates a new AudioDeviceHandler and sets up the basics 123 adh.initAudioDevice(-1); //Initializes the default device 124 modMan = new ModuleManager(adh); //Initializes the module manager 125 modCfg = new ModuleConfig(modMan); //Initializes the module configurator 126 modCfg.loadConfigFromFile("yourAudioConfiguration.sdl");//This line loads an audio configuration file (make sure you have a valid one - create one with the ADK/test1!) 127 modCfg.compile(false); //Compiles the current module configuration. 128 midiSeq = new SequencerM1(modMan.moduleList, modCfg.midiRouting, modCfg.midiGroups); 129 modMan.runAudioThread(); //Runs the audio thread. 130 //audio related part end 131 132 //<Put other initialization code here> 133 } 134 void whereTheMagicHappens() { 135 while (stateFlags.isRunning) { 136 //Refreshes the raster, then sends the new image to the output window. 137 rstr.refresh(); 138 //Tests the input devices for events. 139 ih.test(); 140 //Tests the timer for any registered events that are to happen. 141 //Note: You can put this call into a separate thread for more precision. 142 timer.test(); 143 //Calling the RNG for every frame will make it less deterministic. Speed runners might hate you for this. 144 rng(); 145 //This calls the collision detector on all registered objects. 146 //You'll want to only call it on moving objects, especially when you have a lot of objects on screen. 147 ocd.testAll(); 148 149 //<Per-frame code comes here> 150 } 151 } 152 ///This function will load a map file to display levels, or portions of levels. 153 ///If you're clever, you can store other things in map files 154 void loadMap(string m) { 155 //This loop removes all previously loaded layers from the raster. 156 foreach (key, elem; mapSource.layeroutput) { 157 rstr.removeLayer(key); 158 } 159 mapSource = new MapFormat(File(m)); //Loads the map file itself, and parses it. 160 mapSource.loadTiles(rstr); //Loads the tiles with the palettes needed to display them. 161 mapSource.loadAllSpritesAndObjects(rstr, ocd); //Loads all sprites and objects to the layers, and the collision detector. Also loads the palettes for the sprites 162 mapSource.loadMappingData(); //Loads all mapping data to the tilelayers. 163 rstr.loadLayers(mapSource.layeroutput); //Adds the layers to the raster for display. 164 //Stores a reference to the gamefield. 165 //Change the index number if you want. 166 gameField = cast(SpriteLayer)(mapSource.layeroutput[16]); 167 } 168 ///Collision events can be handled from here. 169 public void onCollision(ObjectCollisionEvent event) { 170 171 } 172 ///Called if the window is closed, etd. 173 public void onQuit() { 174 //You might want to put here a more complicated prompt instead. 175 stateFlags.isRunning = false; 176 } 177 ///Called when a controller is added to the system. 178 ///Note: This function will be changed once I move input handling and output screen handling to iota. 179 public void controllerAdded(uint id) { 180 181 } 182 ///Called when a controller is removed the system. 183 ///Note: This function will be changed once I move input handling and output screen handling to iota. 184 public void controllerRemoved(uint id) { 185 186 } 187 ///Called if a key input event has occured. 188 ///Note: This function will be changed once I move input handling and output screen handling to iota. 189 public void keyEvent(uint id, BindingCode code, uint timestamp, bool isPressed) { 190 191 } 192 ///Called if an axis input event has occured. 193 ///Note: This function will be changed once I move input handling and output screen handling to iota. 194 public void axisEvent(uint id, BindingCode code, uint timestamp, float value) { 195 196 } 197 }