v0.9.4: added iOS module!

Note that this commit includes several changes made iteratively over time, from TestFlight 1.0 to 1.5
This commit is contained in:
Evan Debenham
2021-06-25 22:45:09 -04:00
parent 0c2434a605
commit 7b08b4d5bd
29 changed files with 729 additions and 1 deletions
@@ -0,0 +1,103 @@
package com.shatteredpixel.shatteredpixeldungeon.ios;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.backends.iosrobovm.IOSApplication;
import com.badlogic.gdx.backends.iosrobovm.IOSApplicationConfiguration;
import com.badlogic.gdx.graphics.glutils.HdpiMode;
import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
import com.shatteredpixel.shatteredpixeldungeon.services.news.News;
import com.shatteredpixel.shatteredpixeldungeon.services.news.NewsImpl;
import com.shatteredpixel.shatteredpixeldungeon.services.updates.UpdateImpl;
import com.shatteredpixel.shatteredpixeldungeon.services.updates.Updates;
import com.watabou.noosa.Game;
import com.watabou.utils.FileUtils;
import org.robovm.apple.coregraphics.CGRect;
import org.robovm.apple.foundation.NSAutoreleasePool;
import org.robovm.apple.foundation.NSBundle;
import org.robovm.apple.foundation.NSDictionary;
import org.robovm.apple.foundation.NSException;
import org.robovm.apple.glkit.GLKViewDrawableColorFormat;
import org.robovm.apple.glkit.GLKViewDrawableDepthFormat;
import org.robovm.apple.uikit.UIApplication;
public class IOSLauncher extends IOSApplication.Delegate {
@Override
protected IOSApplication createApplication() {
//ensures the app actually crashes if there's an error in the mobiVM runtime
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable ex) {
new NSException(ex.getClass().getName(), ex.getMessage(), new NSDictionary()).raise();
}
});
try {
Game.version = NSBundle.getMainBundle().getInfoDictionaryObject("CFBundleVersionString").toString();
} catch (Exception e) {
Game.version = "???";
}
try {
Game.versionCode = Integer.parseInt(NSBundle.getMainBundle().getInfoDictionaryObject("CFBundleVersion").toString());
} catch (Exception e) {
Game.versionCode = 0;
}
if (UpdateImpl.supportsUpdates()) {
Updates.service = UpdateImpl.getUpdateService();
}
if (NewsImpl.supportsNews()) {
News.service = NewsImpl.getNewsService();
}
FileUtils.setDefaultFileProperties(Files.FileType.Local, "");
IOSApplicationConfiguration config = new IOSApplicationConfiguration();
config.colorFormat = GLKViewDrawableColorFormat.RGBA8888;
config.depthFormat = GLKViewDrawableDepthFormat.None;
config.hdpiMode = HdpiMode.Pixels;
CGRect statusBarFrame = UIApplication.getSharedApplication().getStatusBarFrame();
double statusBarHeight = Math.min(statusBarFrame.getWidth(), statusBarFrame.getHeight());
//if the application has a short status bar (no notch), then hide it
//TODO we do this check elsewhere now, can this be removed?
if (statusBarHeight <= 24) {
UIApplication.getSharedApplication().setStatusBarHidden(true);
}
config.useAccelerometer = false;
config.useCompass = false;
//devices not currently listed in LibGDX's IOSDevice class
config.addIosDevice("IPHONE_12_MINI", "iPhone13,1", 476);
config.addIosDevice("IPHONE_12", "iPhone13,2", 460);
config.addIosDevice("IPHONE_12_PRO", "iPhone13,3", 460);
config.addIosDevice("IPHONE_12_PRO_MAX", "iPhone13,4", 458);
config.addIosDevice("IPAD_7G_WIFI", "iPad7,11", 264);
config.addIosDevice("IPAD_7G_WIFI_CELLULAR", "iPad7,12", 264);
config.addIosDevice("IPAD_8G_WIFI", "iPad11,6", 264);
config.addIosDevice("IPAD_8G_WIFI_CELLULAR", "iPad11,7", 264);
config.addIosDevice("IPAD_AIR_4G_WIFI", "iPad13,1", 264);
config.addIosDevice("IPAD_AIR_4G_WIFI_CELLULAR", "iPad13,2", 264);
config.addIosDevice("IPAD_PRO_11_3G", "iPad13,4", 264);
config.addIosDevice("IPAD_PRO_11_3G", "iPad13,5", 264);
config.addIosDevice("IPAD_PRO_11_3G", "iPad13,6", 264);
config.addIosDevice("IPAD_PRO_11_3G", "iPad13,7", 264);
config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,8", 264);
config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,9", 264);
config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,10", 264);
config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,11", 264);
return new IOSApplication(new ShatteredPixelDungeon(new IOSPlatformSupport()), config);
}
public static void main(String[] argv) {
NSAutoreleasePool pool = new NSAutoreleasePool();
UIApplication.main(argv, null, IOSLauncher.class);
pool.close();
}
}
@@ -0,0 +1,235 @@
package com.shatteredpixel.shatteredpixeldungeon.ios;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.iosrobovm.IOSGraphics;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.PixmapPacker;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
import com.watabou.noosa.Game;
import com.watabou.utils.PlatformSupport;
import org.robovm.apple.audiotoolbox.AudioServices;
import org.robovm.apple.systemconfiguration.SCNetworkReachability;
import org.robovm.apple.systemconfiguration.SCNetworkReachabilityFlags;
import org.robovm.apple.uikit.UIApplication;
import java.util.HashMap;
import java.util.regex.Pattern;
public class IOSPlatformSupport extends PlatformSupport {
@Override
public void updateDisplaySize() {
//non-zero safe insets on left/top/right means device has a notch, show status bar
if (Gdx.graphics.getSafeInsetTop() != 0
|| Gdx.graphics.getSafeInsetLeft() != 0
|| Gdx.graphics.getSafeInsetRight() != 0){
UIApplication.getSharedApplication().setStatusBarHidden(false);
} else {
UIApplication.getSharedApplication().setStatusBarHidden(true);
}
if (!SPDSettings.fullscreen()) {
Game.bottomInset = Gdx.graphics.getSafeInsetBottom();
Game.height -= Game.bottomInset;
Game.dispHeight = Game.height;
} else {
Game.height += Game.bottomInset;
Game.dispHeight = Game.height;
Game.bottomInset = 0;
}
Gdx.gl.glViewport(0, Game.bottomInset, Game.width, Game.height);
}
@Override
public void updateSystemUI() {
updateDisplaySize();
ShatteredPixelDungeon.seamlessResetScene();
}
@Override
public boolean connectedToUnmeteredNetwork() {
SCNetworkReachability test = new SCNetworkReachability("www.apple.com");
return !test.getFlags().contains(SCNetworkReachabilityFlags.IsWWAN);
}
@Override
public void promptTextInput(String title, String hintText, int maxLen, boolean multiLine, String posTxt, String negTxt, TextCallback callback) {
//TODO need multiplat text input, this does nothing atm!
}
public void vibrate( int millis ){
//gives a short vibrate on iPhone 6+, no vibration otherwise
AudioServices.playSystemSound(1520);
}
private int pageSize;
private PixmapPacker packer;
private boolean systemfont;
//custom pixel font, for use with Latin and Cyrillic languages
private static FreeTypeFontGenerator basicFontGenerator;
private static HashMap<Integer, BitmapFont> basicFonts = new HashMap<>();
//droid sans fallback, for asian fonts
private static FreeTypeFontGenerator asianFontGenerator;
private static HashMap<Integer, BitmapFont> asianFonts = new HashMap<>();
private static HashMap<FreeTypeFontGenerator, HashMap<Integer, BitmapFont>> fonts;
@Override
public void setupFontGenerators(int pageSize, boolean systemfont) {
//don't bother doing anything if nothing has changed
if (fonts != null && this.pageSize == pageSize && this.systemfont == systemfont){
return;
}
this.pageSize = pageSize;
this.systemfont = systemfont;
if (fonts != null){
for (FreeTypeFontGenerator generator : fonts.keySet()){
for (BitmapFont f : fonts.get(generator).values()){
f.dispose();
}
fonts.get(generator).clear();
generator.dispose();
}
fonts.clear();
if (packer != null){
for (PixmapPacker.Page p : packer.getPages()){
p.getTexture().dispose();
}
packer.dispose();
}
}
fonts = new HashMap<>();
if (systemfont) {
basicFontGenerator = asianFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/droid_sans.ttf"));
} else {
basicFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/pixel_font.ttf"));
asianFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/droid_sans.ttf"));
}
fonts.put(basicFontGenerator, basicFonts);
fonts.put(asianFontGenerator, asianFonts);
packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 1, false);
}
@Override
public void resetGenerators() {
if (fonts != null) {
for (FreeTypeFontGenerator generator : fonts.keySet()) {
for (BitmapFont f : fonts.get(generator).values()) {
f.dispose();
}
fonts.get(generator).clear();
generator.dispose();
}
fonts.clear();
if (packer != null) {
for (PixmapPacker.Page p : packer.getPages()) {
p.getTexture().dispose();
}
packer.dispose();
}
fonts = null;
}
setupFontGenerators(pageSize, systemfont);
}
@Override
public void reloadGenerators() {
if (packer != null) {
for (PixmapPacker.Page p : packer.getPages()) {
p.getTexture().dispose();
p.updateTexture(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest, false);
}
}
}
private static Pattern asianMatcher = Pattern.compile("\\p{InHangul_Syllables}|" +
"\\p{InCJK_Unified_Ideographs}|\\p{InCJK_Symbols_and_Punctuation}|\\p{InHalfwidth_and_Fullwidth_Forms}|" +
"\\p{InHiragana}|\\p{InKatakana}");
private static FreeTypeFontGenerator getGeneratorForString( String input ){
if (asianMatcher.matcher(input).find()){
return asianFontGenerator;
} else {
return basicFontGenerator;
}
}
@Override
public BitmapFont getFont(int size, String text) {
FreeTypeFontGenerator generator = getGeneratorForString(text);
if (generator == null){
return null;
}
if (!fonts.get(generator).containsKey(size)) {
FreeTypeFontGenerator.FreeTypeFontParameter parameters = new FreeTypeFontGenerator.FreeTypeFontParameter();
parameters.size = size;
parameters.flip = true;
parameters.borderWidth = parameters.size / 10f;
if (size >= 20){
parameters.renderCount = 2;
} else {
parameters.renderCount = 3;
}
parameters.hinting = FreeTypeFontGenerator.Hinting.None;
parameters.spaceX = -(int) parameters.borderWidth;
parameters.incremental = true;
if (generator == basicFontGenerator){
//if we're using latin/cyrillic, we can safely pre-generate some common letters
//(we define common as >4% frequency in english)
parameters.characters = "etaoinshrdl";
} else {
parameters.characters = "";
}
parameters.packer = packer;
try {
BitmapFont font = generator.generateFont(parameters);
font.getData().missingGlyph = font.getData().getGlyph('');
fonts.get(generator).put(size, font);
} catch ( Exception e ){
Game.reportException(e);
return null;
}
}
return fonts.get(generator).get(size);
}
//splits on newlines, underscores, and chinese/japaneses characters
private Pattern regularsplitter = Pattern.compile(
"(?<=\n)|(?=\n)|(?<=_)|(?=_)|" +
"(?<=\\p{InHiragana})|(?=\\p{InHiragana})|" +
"(?<=\\p{InKatakana})|(?=\\p{InKatakana})|" +
"(?<=\\p{InCJK_Unified_Ideographs})|(?=\\p{InCJK_Unified_Ideographs})|" +
"(?<=\\p{InCJK_Symbols_and_Punctuation})|(?=\\p{InCJK_Symbols_and_Punctuation})");
//additionally splits on words, so that each word can be arranged individually
private Pattern regularsplitterMultiline = Pattern.compile(
"(?<= )|(?= )|(?<=\n)|(?=\n)|(?<=_)|(?=_)|" +
"(?<=\\p{InHiragana})|(?=\\p{InHiragana})|" +
"(?<=\\p{InKatakana})|(?=\\p{InKatakana})|" +
"(?<=\\p{InCJK_Unified_Ideographs})|(?=\\p{InCJK_Unified_Ideographs})|" +
"(?<=\\p{InCJK_Symbols_and_Punctuation})|(?=\\p{InCJK_Symbols_and_Punctuation})");
@Override
public String[] splitforTextBlock(String text, boolean multiline) {
if (multiline) {
return regularsplitterMultiline.split(text);
} else {
return regularsplitter.split(text);
}
}
}