A lot of comments, assets update

This commit is contained in:
Furkan Mudanyali 2021-12-26 18:54:16 +03:00
parent 287f366972
commit bf5e6c546a
29 changed files with 375 additions and 22 deletions

49
.classpath Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

34
.project Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>DanmakuProject</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
<filteredResources>
<filter>
<id>1640019058211</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding/<project>=UTF-8

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=false

View File

@ -0,0 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.processAnnotations=disabled
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# DanmakuProject
This is made for a school project. It uses SDL2 under Java using JNA library.
The main bindings are from libjsdl which is an attempt to create an interface for SDL2 using JNA
but it lacks documentation and demonstration (still a great project). Some SDL2_mixer bindings are
also created using JNA, which is not part of the libjsdl. (see Audio.java)
I hope this proves to be a good demonstration for using libjsdl and JNA.
## Credits:
* Furkan Mudanyalı (Code, planning, execution etc.)
* graphLoom, (Music, assets)
### Further Credits:
* libjsdl contributors (especially shinedeveloper for a loot of bindings)
* SDL2 contributors
* SDL2_mixer contributors

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/bullets/bulletp.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

BIN
assets/enemies/guardian.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 720 KiB

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -33,7 +33,7 @@ import javax.sound.sampled.AudioSystem;
/**
* <h3>Audio Class</h3>
*
* This class is a native wrapper for SDL2_Mixer,
* This class is a native JNA wrapper for SDL2_Mixer,
* it also contains some additional methods
*
* @author Furkan Mudanyali
@ -127,14 +127,21 @@ public class Audio {
public static int Mix_PlayChannel(int channel, Pointer chunk, int loops){
return Mix_PlayChannelTimed(channel, chunk, loops, -1);
}
// Methods
/**
* Returns WAV music length in milliseconds.
*
* <p>
*
* Credits to mdma from stackoverflow
* <p>
* https://stackoverflow.com/a/3009973
*
* @param filepath WAV file path
* @return length in milliseconds
* @return length in milliseconds, -1 if can't
* open file.
*/
public static long getMusicLengthInMilliseconds(String filepath){
try {

View File

@ -21,10 +21,25 @@ import java.io.File;
import java.util.Objects;
/**
* @param fileName filename to get path of
* @return path to the asset in the assets folder.
* <h3>File Loader Class</h3>
*
* A class that contains static methods for file path
* operations for SDL2. Since SDL2 cannot access the
* virtual filesystem inside the .jar package, SDL2 files
* must be kept in a seperate folder along with the .jar.
*
* @author Furkan Mudanyali
* @version 1.0.0
* @since 2021-12-01
*/
public class FileLoader {
/**
* Returns absolute path to the file from the assets folder.
*
* @param fileName filename to get path of
* @return path to the asset in the assets folder,
* null on any kind of I/O error.
*/
public static String getFilePath(String fileName){
try{
String protocol = FileLoader.class.getResource("").getProtocol();
@ -35,7 +50,7 @@ public class FileLoader {
}
return System.getProperty("user.dir") + "/assets/" + fileName;
} catch (Exception e){
return "could not get filepath";
return null;
}
}
}

View File

@ -20,13 +20,28 @@ package com.fmudanyali;
import static org.libsdl.api.keyboard.SdlKeyboard.*;
import com.sun.jna.ptr.ByteByReference;
/**
* <h3>Keyboard Class</h3>
*
* A class to simplify keyboard functions.
*
* @author Furkan Mudanyali
* @version 1.0.0
* @since 2021-12-01
*/
public class Keyboard {
// Basically array pointer
public static ByteByReference keyboard = new ByteByReference();
public static void getKeyboardState(){
keyboard = SDL_GetKeyboardState(null);
}
/**
* Checks if given index of the keyboard array is true or not.
*
* @param key Index of the key in the keyboard array.
* @return true if key is pressed, false if not.
*/
public static boolean getKeyState(int key){
return keyboard.getPointer().getByte(key) == 1;
}

View File

@ -21,23 +21,49 @@ import com.fmudanyali.scenes.*;
import java.util.Stack;
import org.libsdl.api.event.events.SDL_Event;
/**
* <h3>Main Class</h3>
* This is the starting point of the application.
*
* @author Furkan Mudanyali
* @version 1.0.0
* @since 2021-12-01
*/
public class Main {
public static SDL_Event e = new SDL_Event();
public static boolean exit = false;
public static Stack<Scene> scenes = new Stack<>();
public static void main(String[] args) throws Exception{
/*
Restart JVM if on macOS and it hasn't been launched
with -XStartOnFirstThread arg. Needed for SDL to be
able to create a window and show it on macOS as macOS
ignores windows that are not opened by the first thread
of the application for safety measures. Took me days
to track the bug.
*/
if (RestartJVM.restartJVM()) {
return;
}
// Open audio device with basic defaults.
Audio.Mix_OpenAudio(44100, Audio.MIX_DEFAULT_FORMAT, 2, 2048);
// Push main menu to the scenes stack.
scenes.push(new MainMenu());
// Each loop means a frame of the game.
while(!scenes.empty() && !exit){
// Calculate delta time, between last frame
// and this frame.
Time.Tick();
Keyboard.getKeyboardState();
// Call the loop function of the scene on the
// top of the scenes stack.
scenes.peek().loop();
}
// Exit is set to true manually to make sure the existing threads
// are stopped, not really necessary as System.exit should close
// those lingering threads but better be safe than sorry.
exit = true;
System.exit(0);
}

View File

@ -33,24 +33,50 @@ import static org.libsdl.api.hints.SdlHintsConst.*;
import static org.libsdl.api.hints.SdlHints.*;
import static org.libsdl.api.render.SdlRender.SDL_RenderSetLogicalSize;
/**
* <h3>Render Class</h3>
* This class contains and initializes the SDL_Window and SDL_Renderer pointers,
* it also has a method to create a continuous background from given texture.
*
* @author Furkan Mudanyali
* @version 0.2.0
* @since 2021-12-01
*/
public class Render {
public static SDL_Window window;
public static SDL_Renderer renderer;
public static int bgw, bgh;
// Initialize SDL_Window and SDL_Renderer
static {
// Create window
window = SDL_CreateWindow("DanmakuProject SDL2",
SDL_WINDOWPOS_CENTERED(), SDL_WINDOWPOS_CENTERED(),
WIDTH, HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
// Hints for the renderer
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
// Create renderer
renderer =
SDL_CreateRenderer(Render.window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
// Make content scale according to the window size while maintaining the aspect ratio.
SDL_RenderSetLogicalSize(renderer, Screen.WIDTH, Screen.HEIGHT);
}
/**
* Creates a new row x col texture
* from the given texture by stitching
* it together.
*
* @param renderer Renderer to be used
* @param texture Texture to be repeated
* @param cols How many columns of the texture
* @param rows How many rows of the texture
* @return
*/
public static SDL_Texture createBackgroundFromTexture(
SDL_Renderer renderer, SDL_Texture texture, int cols, int rows
){

View File

@ -25,10 +25,22 @@ import java.io.InputStreamReader;
import java.io.BufferedReader;
/**
* Credits to kappa From
* <h3>RestartJVM Class</h3>
*
* Has a single function that restarts JVM on macOS
* if JVM is not launched with -XstartOnFirstThread arg
* and launches it with it.
*
* <p>
*
* Credits to kappa from jvm-gaming.org
* <p>
* https://jvm-gaming.org/t/starting-jvm-on-mac-with-xstartonfirstthread-programmatically/57547
*
* @author kappa
* @version 1.0.0
* @since 2016-08-26
*/
public class RestartJVM {
public static boolean restartJVM() {

View File

@ -27,6 +27,16 @@ import com.fmudanyali.characters.Player;
import static org.libsdl.api.render.SdlRender.*;
/**
* <h3>Screen Class</h3>
*
* This class contains information about the game screen
* It also has a method to set its background texture, and scroll it.
*
* @author Furkan Mudanyali
* @version 0.2.0
* @since 2021-12-08
*/
public class Screen {
public static final int WIDTH = 960;
public static final int HEIGHT = 540;
@ -38,6 +48,7 @@ public class Screen {
public static SDL_Texture tile, background, wallpaper;
public static SDL_Surface tempSurface;
// Calculate positions and load wallpaper image.
static {
canvas.x = 12;
canvas.y = 0;
@ -54,6 +65,10 @@ public class Screen {
tempSurface = null;
}
/**
* Loads the given filepath to the background.
* @param filename file path of the image.
*/
public static void makeBackground(String filename){
// Load tile
tempSurface = SDL_LoadBMP(FileLoader.getFilePath(filename));
@ -64,6 +79,12 @@ public class Screen {
background = Render.createBackgroundFromTexture(Render.renderer, tile, 1, 2);
}
/**
* Scrolls the background vertically and horizontally.
* Horizontal scroll is based on the player position to
* create a parallax effect.
* @param player
*/
public static void scroll(Player player){
canvas.y = Math.floorMod(canvas.y - (int)(Time.deltaTime * 0.1), Render.bgh - canvas.h);
canvas.x = (int)(12 + (player.position.x - 464)/13);

View File

@ -19,6 +19,16 @@ package com.fmudanyali;
import static org.libsdl.api.timer.SdlTimer.*;
/**
* <h3>Time Class</h3>
*
* Calculates the time difference between this frame
* and last frame, deltatime.
*
* @author Furkan Mudanyali
* @version 1.0.0
* @since 2021-12-03
*/
public class Time {
public static int lastTime = 0;
public static int currentTime = 0;

View File

@ -28,13 +28,27 @@ import org.libsdl.api.surface.SDL_Surface;
import static org.libsdl.api.surface.SdlSurface.*;
import static org.libsdl.api.render.SdlRender.*;
/**
* <h3>PlayerBullet Class</h3>
*
* This class is for player made projectiles
* that advance forward in the screen and explode
* on enemy contact (to be implemented).
*
* @author Furkan Mudanyali
* @version 0.4.0
* @since 2021-12-22
*/
public class PlayerBullet {
// Load the texture
public static SDL_Surface tempSurface = SDL_LoadBMP(FileLoader.getFilePath("player/bullet.bmp"));
public static SDL_Texture texture = SDL_CreateTextureFromSurface(Render.renderer, tempSurface);
public SDL_Rect position = new SDL_Rect();
public int width = 5;
public int height = 12;
// Set the bullet position relative to the player position and given
// offsets.
public PlayerBullet(SDL_Rect playerPos, int xOffset, int yOffset){
position.w = width;
position.h = height;
@ -42,6 +56,9 @@ public class PlayerBullet {
position.y = playerPos.y - 6 - 3 - yOffset;
}
/**
* Advances the bullet on the screen.
*/
public void fly(){
position.y = position.y - (int)(Time.deltaTime * 0.5);
}

View File

@ -21,6 +21,16 @@ import org.libsdl.api.rect.SDL_Rect;
import org.libsdl.api.render.SDL_Texture;
import org.libsdl.api.surface.SDL_Surface;
/**
* <h3>Character Superclass</h3>
*
* This class provides essentials that a character
* should require.
*
* @author Furkan Mudanyali
* @version 1.0.0
* @since 2021-12-08
*/
public class Character {
public SDL_Rect position = new SDL_Rect();
public SDL_Texture texture = new SDL_Texture();

View File

@ -18,5 +18,5 @@
package com.fmudanyali.characters;
public class Enemy extends Character {
// TODO: Implement
}

View File

@ -32,7 +32,19 @@ import static org.libsdl.api.surface.SdlSurface.*;
import static org.libsdl.api.render.SdlRender.*;
import static org.libsdl.api.scancode.SDL_Scancode.*;
/**
* <h3>Player Class</h3>
*
* This class extends on the character class and has all the
* bells and whistles such as handling movement, shooting bullets,
* animated sprites etc.
*
* @author Furkan Mudanyali
* @version 0.9.0
* @since 2021-12-08
*/
public class Player extends Character {
// Variables
public int lives;
public double speed;
public SDL_Texture propeller;
@ -40,14 +52,16 @@ public class Player extends Character {
public int frame = 0;
public int roll = 0;
public int cooldown = 0;
// New position rectangles for additional parts
public SDL_Rect propellerPos = new SDL_Rect();
public SDL_Rect shooterPos = new SDL_Rect();
// These rectangles are used for shifting
// sprite animation.
public SDL_Rect shipFrame = new SDL_Rect();
public SDL_Rect propellerFrame = new SDL_Rect();
public SDL_Rect shooterFrame = new SDL_Rect();
// Load the textures and set the positions.
public Player(){
lives = 3;
tempSurface = SDL_LoadBMP(FileLoader.getFilePath("player/ship.bmp"));
@ -167,7 +181,8 @@ public class Player extends Character {
propellerFrame.x = 10;
break;
}
// Not a switch as DeltaTime does not provide
// linear increase.
if(roll > 0){
shipFrame.x = shipFrame.y = 0;
} if(roll > 15){
@ -178,17 +193,23 @@ public class Player extends Character {
} if(roll > 45){
shipFrame.x = 32;
}
// Frame is kept in 600 instead of 60
// to be accurate with the varying framerate
// animations.
frame = (frame+10)%600;
}
/**
* Changes positions depending on the keyboard state.
*/
public void movement(){
// If shift is pressed, slow down the player.
if(Keyboard.getKeyState(SDL_SCANCODE_LSHIFT)){
speed = 1.5;
}else{
speed = 2;
}
// Bunch of mathematical mumbo jumbo.
if(Keyboard.getKeyState(SDL_SCANCODE_A) | Keyboard.getKeyState(SDL_SCANCODE_LEFT)){
position.x = Math.max(position.x - (int)(speed * Time.deltaTime * 0.1), Screen.canvasPos.x - 5);
shooterPos.x = Math.max(shooterPos.x - (int)(speed * Time.deltaTime * 0.1), Screen.canvasPos.x + 16 - 4 - 5);
@ -227,7 +248,8 @@ public class Player extends Character {
Game.playerBullets.add(new PlayerBullet(position, 17, 0));
Game.playerBullets.add(new PlayerBullet(position, 21, 6));
Game.playerBullets.add(new PlayerBullet(position, 25, 9));
cooldown = 1;
// Cooldown is used to prevent overshooting.
cooldown = 3;
}
cooldown = Math.max(cooldown -1, 0);
}

View File

@ -33,51 +33,85 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* <h3>Game Class</h3>
*
* This class extends on the Scene class and is mainly
* the core of the game.
*
* @author Furkan Mudanyali
* @version 0.1.0
* @since 2021-12-04
*/
public class Game extends Scene {
// Variables
public static boolean escPressed = false;
int kek = 0;
private Player player = new Player();
// Keeping track of the shots fired
public static List<PlayerBullet> playerBullets = new ArrayList<>();
// Thread for bg music
private static Thread thread;
// Create a runnable for the thread
private static Runnable runnable = new Runnable() {
public void run(){
try {
Mix_PlayMusic(Mix_LoadMUS(FileLoader.getFilePath("80-search-intro.wav")), 1);
// Sleeping for the duration of the intro instead of checking if it stopped on a
// loop saves a loooot of clock cycles.
Thread.sleep(getMusicLengthInMilliseconds(FileLoader.getFilePath("80-search-intro.wav")));
Mix_PlayMusic(Mix_LoadMUS(FileLoader.getFilePath("80-search-loop.wav")), -1);
// Kill self because the last music will loop forever
Thread.currentThread().interrupt();
return;
} catch(Exception e){
// Will throw sleep interrupted, which is what we want.
// Will throw sleep interrupted, which is exactly what we want.
}
}
};
public Game(){
// Create the looping background
Screen.makeBackground("shmap.bmp");
// Set music volume to max
Mix_VolumeMusic(128);
// Try reading thread state, will throw exception for
// the first time as the thread will not be initialized
// at that time, which is perfectly expected.
try {
// Interrupt the thread if its running, this is needed
// to keep only one thread alive at a time because if
// the game is immediately quitted and a new game started,
// the music will not get confused.
if(thread.getState() != Thread.State.TERMINATED){
thread.interrupt();
}
} catch (Exception e){
//
// Do nothing as I expect this behavior.
}
// Overwrite the the TERMINATED (or not initialized)
// thread with a new thread with our runnable.
thread = new Thread(runnable);
// Start the thread.
thread.start();
}
@Override
public void loop(){
// Handle events first
while(SDL_PollEvent(Main.e) != 0){
switch(Main.e.type){
// If close button on the window is pressed.
case SDL_QUIT:
Main.exit = true;
break;
// If a key is pressed
case SDL_KEYDOWN:
switch(Main.e.key.keysym.sym){
// If ESC is pressed, push pause menu to the scenes stack
case SDLK_ESCAPE:
// Needed to execute it only once per conventional key press
if(!escPressed){
Main.scenes.push(new PauseMenu());
Mix_VolumeMusic(30);
@ -86,6 +120,7 @@ public class Game extends Scene {
break;
}
break;
// If a key is released
case SDL_KEYUP:
switch(Main.e.key.keysym.sym){
case SDLK_ESCAPE:
@ -96,27 +131,31 @@ public class Game extends Scene {
}
}
// Do player movement thing and scroll the screen
player.movement();
Screen.scroll(player);
// Copying stuff to the renderer
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, Screen.wallpaper, null, null);
SDL_RenderCopy(renderer, Screen.background, Screen.canvas, Screen.canvasPos);
SDL_RenderCopy(renderer, player.texture, player.shipFrame, player.position);
SDL_RenderCopy(renderer, player.propeller, player.propellerFrame, player.propellerPos);
SDL_RenderCopy(renderer, player.shooter, player.shooterFrame, player.shooterPos);
// Iterate over the bullets on the screen
for(Iterator<PlayerBullet> bulletIterator = playerBullets.iterator(); bulletIterator.hasNext();){
PlayerBullet b = bulletIterator.next();
// Move the bullet
b.fly();
// If the bullet is out of the screen, destroy it, else copy it to the renderer.
if(b.position.y < 30){
bulletIterator.remove();
} else {
SDL_RenderCopy(renderer, PlayerBullet.texture, null, b.position);
}
}
// Present the renderer to the window.
SDL_RenderPresent(renderer);
// Advance player sprite animation
player.shiftFrame();
}
}

View File

@ -17,6 +17,20 @@
package com.fmudanyali.scenes;
/**
* <h3>Scene Class</h3>
*
* This class is an abstract for other scenes and makes able to
* keep all the different kind of scenes on a single stack.
*
* @author Furkan Mudanyali
* @version 1.0.0
* @since 2021-12-04
*/
public abstract class Scene {
/**
* By design, this function will be
* executed each game frame.
*/
public abstract void loop();
}