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 }