skunkworks/com/danitheskunk/skunkworks/BaseGame.java

202 lines
5.6 KiB
Java

package com.danitheskunk.skunkworks;
import com.danitheskunk.skunkworks.audio.AudioEngine;
import com.danitheskunk.skunkworks.audio.ISample;
import com.danitheskunk.skunkworks.audio.nodes.Mixer;
import com.danitheskunk.skunkworks.audio.nodes.Node;
import com.danitheskunk.skunkworks.audio.nodes.SamplePlayer;
import com.danitheskunk.skunkworks.audio.nodes.TTS;
import com.danitheskunk.skunkworks.backends.gl.Gamepad;
import com.danitheskunk.skunkworks.gfx.Color;
import com.danitheskunk.skunkworks.gfx.IRenderContext;
import com.danitheskunk.skunkworks.gfx.ITexture;
import com.danitheskunk.skunkworks.gfx.font.IFont;
import com.danitheskunk.skunkworks.gfx.threedee.IRenderContext3D;
import com.danitheskunk.skunkworks.nodes.NodeRoot;
import org.lwjgl.glfw.GLFW;
/**
* Main class to extend to write a game in Skunkworks
* <p>
* Extend this class and override {@link BaseGame#update update},
* {@link BaseGame#render render}, {@link BaseGame#renderPre renderPre},
* and/or {@link BaseGame#render3D render3D} to add functionality. Then instance
* and call {@link BaseGame#run run}.
* <p>
* Automatic timing (by default 60fps), resizing(with various scalers), 2d
* and 3d rendering, and audio playback.
*/
@SuppressWarnings("SameParameterValue")
public abstract class BaseGame {
protected final AudioEngine audioEngine;
protected final IFont debugFont;
protected final Engine engine;
protected final Mixer mixer;
protected final NodeRoot rootNode;
protected final SamplePlayer samplePlayer;
protected final TTS tts;
protected final IWindow window;
protected final BaseGamepad gamepad;
/**
* Create with given window size and title
*
* @param windowSize the size of the game's window
* @param windowTitle the title of the game's window
*/
public BaseGame(Vec2i windowSize, String windowTitle) {
audioEngine = new AudioEngine(48000, 256, 8);
mixer = new Mixer(audioEngine, 2);
samplePlayer = new SamplePlayer(audioEngine);
tts = new TTS(audioEngine);
Node.connect(samplePlayer, 0, mixer, 0);
Node.connect(tts, 0, mixer, 1);
audioEngine.setNode(mixer);
engine = new Engine();
window = engine.openWindow(windowSize, windowTitle);
gamepad = window.getGamepad(0);
//todo: load from .jar
debugFont = window.loadFontTileset("fonts/ega-8x14.png");
rootNode = new NodeRoot();
}
/**
* Load sample from path
*
* @param path file path to the sample
* @return the loaded sample
*/
protected ISample loadSample(String path) {
return audioEngine.loadSample(path);
}
/**
* Load image from path and prepare as gpu texture
*
* @param path file path to the image file
* @return the loaded texture
*/
protected ITexture loadTexture(String path) {
return window.loadTexture(path);
}
/**
* Play sample once
*
* @param sample the sample to be played
*/
protected void playSample(ISample sample) {
samplePlayer.play(sample);
}
/**
* Play sample once or looped
*
* @param sample the sample to be played
* @param looping loops forever if true, otherwise play once
*/
protected void playSample(ISample sample, boolean looping) {
samplePlayer.play(sample, looping);
}
/**
* Render Callback
* <p>
* This method will be called after {@link BaseGame#render3D render3D},
* when the engine is ready for 2d rendering. Override this method to
* render 2d things on screen.
*
* @param rc the render context with all the render functions
*/
protected void render(IRenderContext rc) {
//to be overriden by child class
}
/**
* 3D Render Callback
* <p>
* This method will be called when the engine is ready for 3d rendering.
* Override this method to render 3d things on screen.
*
* @param rc3d the render context with all the render functions
*/
protected void render3D(IRenderContext3D rc3d) {
//to be overriden by child class
}
/**
* Early Render Callback
* <p>
* This method will be called before {@link BaseGame#render3D render3D},
* when the engine is ready for 2d rendering. Override this method to
* render 2d things on screen underneath 3d content.
*
* @param rc the render context with all the render functions
*/
protected void renderPre(IRenderContext rc) {
//to be overriden by child class
}
/**
* Runs the game
* <p>
* This will transfer the control to Skunkworks until the game window
* closes.
*/
public void run() {
double lastTime, currentTime, delta, currentFrameTime;
currentFrameTime = 0;
lastTime = GLFW.glfwGetTime();
while(!window.shouldClose()) {
currentTime = GLFW.glfwGetTime();
delta = currentTime - lastTime;
lastTime = currentTime;
currentFrameTime += delta;
engine.tick();
window.tick();
gamepad.tick();
//todo: frame rate control
if(currentFrameTime >= 1.0 / 60.0) {
rootNode.tick();
update(1.0 / 60.0);
var p3d = window.getPipeline3D();
var p2d = window.getPipeline2D();
window.startFrame();
p3d.startFrame();
//todo: should be transparent but bugged
p3d.getRenderContext().clear(Color.BLACK);
render3D(p3d.getRenderContext());
p3d.finishFrame();
p2d.startFrame();
render(p2d.getRenderContext());
rootNode.render(p2d.getRenderContext());
p2d.finishFrame();
window.finishFrame();
currentFrameTime -= 1.0 / 60.0;
} else {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
audioEngine.refill();
}
}
/**
* Update Callback
* <p>
* This method will be called periodically, either in a fixed(by default)
* or variable period. Can be changed by setting {@link Timestep}
*
* @param delta time that passed since last update in seconds
*/
protected void update(double delta) {
}
}