Continuing to look at converting the old “system” module, next I will add some hardware input handling.
The first thing I need to do here is some cleanup of the input handling in Core.cs. Madbit’s template is already providing event handlers to capture hardware input and pass it through to agk, I’m going to make use of these handlers, but I’m not going to bother with the passthrough. I will be handling all of the input and logic in the C# layer so there is no need to be able to access raw input values from agk methods.
For games, I’m a strong believer in disconnecting input from action and prefer to interject a state layer between them. Instead of something like:
If GetRawKeyState(uparrow)
Move character forward…
endif
I do something like:
…hardware controller
uparrow.wasDown = uparrow.isDown
uparrow.isDown = GetRawKeyState(uparrow)
… player controls
myCharacter.isMoving = uparrow.isDown
…character controller
If thisCharacter.isMoving
Move character forward…
endif
This let’s me easily modify state by many different things such as abilities, the environment, the game state and so on, in addition to raw input before actually resolving the character’s movement. It also lets me handle all character movement for the player, network players, and AI with the same generic character controller.
I’ve previously converted this module from dbpro to T1 agk, so this is what I’ve been working from:
/*
-------------------------------------------------------------------------------------
System Utility
-------------------------------------------------------------------------------------
Author: Josh Kirklin (Ortu) http://joshkirklin.com | http://githubn.com/ortu-
-------------------------------------------------------------------------------------
Contributors:
-------------------------------------------------------------------------------------
Description:
Provides various OS, hardware, timing and performance monitoring processes.
*******************************************
*/
//Declare System -----------------------------------------------------------------
type System_type_timingData
timer as integer
delta as integer
updateMark as integer
pauseState as integer
pauseMark as integer
pauseElapsed as integer
pauseHold as integer
endtype
type System_type_logData
fileNumber as integer
includeLevel as integer
includeChannels as string
endtype
type System_type_mouseInputData
moveX as float
moveY as float
moveZ as float
posX as integer
posY as integer
endtype
type CLASS_SystemInterval
id as string
target as string
mark as integer
interval as integer
doneTicks as integer
maxTicks as integer
callback as string
args as string
endtype
type STATIC_CLASS_System
log as System_type_logData
timing as System_type_timingData
mouse as System_type_mouseInputData
input as integer[222] //keystates :: bit 1 = isDown, bit 2 = wasDown >> TODO: 0 not pressed, 1 new press, 2 new release, 3 held
intervalList as CLASS_SystemInterval[]
endtype
//==================================
GLOBAL System as STATIC_CLASS_System
// ==================================
#CONSTANT BIT_System_pauseHoldSystem 1
#CONSTANT BIT_System_pauseHoldUI 2
//Module Functions ---------------------------------------------------------------
function System_init()
//init log file
//================================================================================================================================================================================
//================================================================================================================================================================================
System.log.includeChannels = "*"
//System.log.includeChannels = "|!|error|main|ai|ui|ui-event|"
//================================================================================================================================================================================
//================================================================================================================================================================================
System.log.includeLevel = App.config.logLevel
System.log.fileNumber = OpenToWrite("log.txt")
WriteLine(System.log.fileNumber, "Runtime | File | Level | Channel | Log")
WriteLine(System.log.fileNumber, "==================================================================")
App_addUpdate("System_updateIntervals", "", FALSE)
endfunction
function System_updateTiming()
System.timing.timer = GetMilliseconds()
System.timing.delta = abs(System.timing.timer - System.timing.delta)
oldPause = System.timing.pauseState
if System.timing.pauseHold = 0
System.timing.pauseState = 0
else
System.timing.pauseState = 1
if oldPause = 0
System.timing.pauseMark = System.timing.timer //new pause
else
System.timing.pauseElapsed = abs(System.timing.timer - System.timing.pauseMark)
endif
endif
if System.timing.pauseState = 2
//pause ended and elapsed time has been applied, clear down
System.timing.pauseState = 0
System.timing.pauseMark = 0
System.timing.pauseElapsed = 0
endif
if System.timing.pauseState = 0 and oldPause = 1
//pause ended, resume all updates but keep the elapsed time available for paused updates to adjust against elapsed times.
System.timing.pauseState = 2
System.mouse.moveX = 0.0
System.mouse.moveY = 0.0
System.mouse.moveZ = 0.0
endif
endfunction
function System_log(rSource as string, rLevel as integer, rChannel as string, rContent as string)
if rLevel >= System.log.includeLevel
if System.log.includeChannels = "*" or FindString(System.log.includeChannels, "|" + rChannel + "|") > 0
WriteLine(System.log.fileNumber, PadRight(System_printfRuntime(), 9) + " | " + PadRight(Left(rSource, 15), 15) + " | " + PadRight(Str(rLevel), 5) + " | " + PadRight(Left(rChannel, 10), 10) + " | " + rContent)
endif
endif
endfunction
function System_printfRuntime()
e as string
h = 0
m = 0
s = 0
t = System.timing.timer
while t > 3600000
inc h
dec t, 3600000
endwhile
while t > 60000
inc m
dec t, 60000
endwhile
while t > 1000
inc s
dec t, 1000
endwhile
e = Str(h) + ":" + Str(m) + ":" + Str(s) + ":" + Str(t)
endfunction e
function System_getHardwareInput()
System.input[ENUM_KEY_MOUSEL] = SetBit(2, System.input[ENUM_KEY_MOUSEL], GetBit(1, System.input[ENUM_KEY_MOUSEL]))
System.input[ENUM_KEY_MOUSEL] = SetBit(1, System.input[ENUM_KEY_MOUSEL], GetRawMouseLeftState())
System.input[ENUM_KEY_MOUSER] = SetBit(2, System.input[ENUM_KEY_MOUSER], GetBit(1, System.input[ENUM_KEY_MOUSER]))
System.input[ENUM_KEY_MOUSER] = SetBit(1, System.input[ENUM_KEY_MOUSER], GetRawMouseRightState())
for i = 3 to System.input.length
System.input[i] = SetBit(2, System.input[i], GetBit(1, System.input[i]))
System.input[i] = SetBit(1, System.input[i], GetRawKeyState(i))
next i
oX = System.mouse.posX
oY = System.mouse.posY
System.mouse.posX = GetRawMouseX()
System.mouse.posY = GetRawMouseY()
System.mouse.moveX = oX - System.mouse.posX
System.mouse.moveY = oY - System.mouse.posY
System.mouse.moveZ = GetRawMouseWheelDelta()
//TODO: touch input, sensor input
endfunction
function System_setInterval(rCallback as string, rArgs as string, rTicks as integer, rInterval as integer, rTarget as string)
tInterval as CLASS_SystemInterval
tInterval.id = Sha256(Str(System.timing.timer) + rCallback + rTarget + rArgs + Str(rTicks) + Str(rInterval))
tInterval.target = rTarget
tInterval.mark = System.timing.timer
tInterval.interval = rInterval
tInterval.doneTicks = 0
tInterval.maxTicks = rTicks
tInterval.callback = rCallback
tInterval.args = rArgs
System.intervalList.insert(tInterval)
endfunction tInterval.id
function System_clearInterval(rID as string)
for i = 0 to System.intervalList.length
if System.intervalList[i].id = rID
System.intervalList.remove(i)
exitfunction
endif
next i
endfunction
function System_getInterval(rCallback as string, rTarget as string)
for i = 0 to System.intervalList.length
if System.intervalList[i].callback = rCallback and System.intervalList[i].target = rTarget
exitfunction i
endif
next i
endfunction -1
function System_updateIntervals()
GCcount = 0
GCindex = -1
tArrCount = System.intervalList.length
if tArrCount > -1
for i = 0 to tArrCount
if System.timing.timer - (System.intervalList[i].mark + System.timing.pauseElapsed) >= System.intervalList[i].interval
System.intervalList[i].mark = System.timing.timer
inc System.intervalList[i].doneTicks
App_callFunction(System.intervalList[i].callback, Str(i) + "," + System.intervalList[i].args)
if System.intervalList[i].maxTicks > -1
if System.intervalList[i].doneTicks >= System.intervalList[i].maxTicks
System.intervalList[i].id = "GC_THIS"
inc GCcount
GCindex = i
endif
endif
endif
next i
if GCcount > 0
if GCcount > 1
for c = 1 to GCcount
for i = 0 to System.intervalList.length
if System.intervalList[i].id = "GC_THIS"
System.intervalList.remove(i)
exit
endif
next i
next c
else
System.intervalList.remove(GCindex)
endif
endif
endif
endfunction
Now this touches on another of the old modules “common” which is largely no longer needed. In particular, the keymap definition can replaced with the key enum in System.Windows
/*
-------------------------------------------------------------------------------------
Standard Constants
-------------------------------------------------------------------------------------
Author: Josh Kirklin (Ortu)
-------------------------------------------------------------------------------------
Contributors:
-------------------------------------------------------------------------------------
Description:
Define common generic constants that can be used to manage raw values by more easily readable names.
*******************************************
Notes:
*/
type keyValueData
key as string
value as string
endtype
#CONSTANT TRUE 1
#CONSTANT FALSE 0
#CONSTANT NEWLINE chr$(13)+chr$(10)
#CONSTANT ENUM_KEY_ESC 27
#CONSTANT ENUM_KEY_F1 112
#CONSTANT ENUM_KEY_F2 113
#CONSTANT ENUM_KEY_F3 114
#CONSTANT ENUM_KEY_F4 115
#CONSTANT ENUM_KEY_F5 116
#CONSTANT ENUM_KEY_F6 117
#CONSTANT ENUM_KEY_F7 118
#CONSTANT ENUM_KEY_F8 119
//#CONSTANT ENUM_KEY_F9
//#CONSTANT ENUM_KEY_F10
//#CONSTANT ENUM_KEY_F11
//#CONSTANT ENUM_KEY_F12
//#CONSTANT ENUM_KEY_PRINT
#CONSTANT ENUM_KEY_SCROLL 145
//#CONSTANT ENUM_KEY_PAUSE
#CONSTANT ENUM_KEY_TILDE 192
#CONSTANT ENUM_KEY_0 48
#CONSTANT ENUM_KEY_1 49
#CONSTANT ENUM_KEY_2 50
#CONSTANT ENUM_KEY_3 51
#CONSTANT ENUM_KEY_4 52
#CONSTANT ENUM_KEY_5 53
#CONSTANT ENUM_KEY_6 54
#CONSTANT ENUM_KEY_7 55
#CONSTANT ENUM_KEY_8 56
#CONSTANT ENUM_KEY_9 57
#CONSTANT ENUM_KEY_DASH 189
#CONSTANT ENUM_KEY_EQUAL 187
#CONSTANT ENUM_KEY_BACKSPACE 8
#CONSTANT ENUM_KEY_TAB 9
#CONSTANT ENUM_KEY_Q 81
#CONSTANT ENUM_KEY_W 87
#CONSTANT ENUM_KEY_E 69
#CONSTANT ENUM_KEY_R 82
#CONSTANT ENUM_KEY_T 84
#CONSTANT ENUM_KEY_Y 89
#CONSTANT ENUM_KEY_U 85
#CONSTANT ENUM_KEY_I 73
#CONSTANT ENUM_KEY_O 79
#CONSTANT ENUM_KEY_P 80
#CONSTANT ENUM_KEY_BRACKETL 219
#CONSTANT ENUM_KEY_BRACKETR 221
#CONSTANT ENUM_KEY_BACKSLASH 220
//#CONSTANT ENUM_KEY_CAPSLOCK
#CONSTANT ENUM_KEY_A 65
#CONSTANT ENUM_KEY_S 83
#CONSTANT ENUM_KEY_D 68
#CONSTANT ENUM_KEY_F 70
#CONSTANT ENUM_KEY_G 71
#CONSTANT ENUM_KEY_H 72
#CONSTANT ENUM_KEY_J 74
#CONSTANT ENUM_KEY_K 75
#CONSTANT ENUM_KEY_L 76
#CONSTANT ENUM_KEY_COLON 186
#CONSTANT ENUM_KEY_QUOTE 222
#CONSTANT ENUM_KEY_ENTER 13
#CONSTANT ENUM_KEY_SHIFTL 16
#CONSTANT ENUM_KEY_Z 90
#CONSTANT ENUM_KEY_X 88
#CONSTANT ENUM_KEY_C 67
#CONSTANT ENUM_KEY_V 86
#CONSTANT ENUM_KEY_B 66
#CONSTANT ENUM_KEY_N 78
#CONSTANT ENUM_KEY_M 77
#CONSTANT ENUM_KEY_COMMA 188
#CONSTANT ENUM_KEY_PERIOD 190
#CONSTANT ENUM_KEY_SLASH 191
#CONSTANT ENUM_KEY_SHIFTR 16
#CONSTANT ENUM_KEY_CTRLL 17
#CONSTANT ENUM_KEY_ALTL 18
#CONSTANT ENUM_KEY_SPACE 32
#CONSTANT ENUM_KEY_ALTR 18
#CONSTANT ENUM_KEY_MENU 93
#CONSTANT ENUM_KEY_CTRLR 17
#CONSTANT ENUM_KEY_INSERT 45
#CONSTANT ENUM_KEY_DELETE 46
#CONSTANT ENUM_KEY_HOME 36
#CONSTANT ENUM_KEY_END 35
#CONSTANT ENUM_KEY_PAGEUP 33
#CONSTANT ENUM_KEY_PAGEDOWN 34
#CONSTANT ENUM_KEY_ARROWUP 38
#CONSTANT ENUM_KEY_ARROWDOWN 40
#CONSTANT ENUM_KEY_ARROWLEFT 37
#CONSTANT ENUM_KEY_ARROWRIGHT 39
//#CONSTANT ENUM_KEY_NUMLOCK
#CONSTANT ENUM_KEY_NUMDIV 192
#CONSTANT ENUM_KEY_NUMMUL 106
#CONSTANT ENUM_KEY_NUMSUB 189
#CONSTANT ENUM_KEY_NUMADD 107
#CONSTANT ENUM_KEY_NUMENTER 13
#CONSTANT ENUM_KEY_NUMDECIMAL 190
#CONSTANT ENUM_KEY_NUM0 48
#CONSTANT ENUM_KEY_NUM1 49
#CONSTANT ENUM_KEY_NUM2 50
#CONSTANT ENUM_KEY_NUM3 51
#CONSTANT ENUM_KEY_NUM4 52
#CONSTANT ENUM_KEY_NUM5 53
#CONSTANT ENUM_KEY_NUM6 54
#CONSTANT ENUM_KEY_NUM7 55
#CONSTANT ENUM_KEY_NUM8 56
#CONSTANT ENUM_KEY_NUM9 57
#CONSTANT ENUM_KEY_MOUSEL 1
#CONSTANT ENUM_KEY_MOUSER 2
function GetBit(rBit as integer, rData as integer)
endfunction (%1 << rBit) && rData
function SetBit(rBit as integer, rData as integer, rVal as integer)
if rVal
rData = rData || (%1 << rBit)
else
if (%1 << rBit) && rData > 0
rData = rData ~~ (%1 << rBit)
endif
endif
endfunction rData
function GetByte(rByte as integer, rData as integer)
endfunction (rData >> (rByte * 8)) && 0xff
function SetByte(rByte as integer, rData as integer, rVal as integer)
endfunction rData || (rVal << (rByte * 8))
function PadRight(rContent as string, rCount as integer)
while Len(rContent) < rCount
rContent = rContent + " "
endwhile
endfunction rContent
function PadLeft(rContent as string, rCount as integer)
while Len(rContent) < rCount
rContent = " " + rContent
endwhile
endfunction rContent
function HexToByte(rHex as string)
tVal = 0
select left(lower(rHex), 1)
case "0": inc tVal, 0 : endcase
case "1": inc tVal, 16 : endcase
case "2": inc tVal, 32 : endcase
case "3": inc tVal, 48 : endcase
case "4": inc tVal, 64 : endcase
case "5": inc tVal, 80 : endcase
case "6": inc tVal, 96 : endcase
case "7": inc tVal, 112 : endcase
case "8": inc tVal, 128 : endcase
case "9": inc tVal, 144 : endcase
case "a": inc tVal, 160 : endcase
case "b": inc tVal, 176 : endcase
case "c": inc tVal, 192 : endcase
case "d": inc tVal, 208 : endcase
case "e": inc tVal, 224 : endcase
case "f": inc tVal, 240 : endcase
endselect
select right(lower(rHex), 1)
case "0": inc tVal, 0 : endcase
case "1": inc tVal, 1 : endcase
case "2": inc tVal, 2 : endcase
case "3": inc tVal, 3 : endcase
case "4": inc tVal, 4 : endcase
case "5": inc tVal, 5 : endcase
case "6": inc tVal, 6 : endcase
case "7": inc tVal, 7 : endcase
case "8": inc tVal, 8 : endcase
case "9": inc tVal, 9 : endcase
case "a": inc tVal, 10 : endcase
case "b": inc tVal, 11 : endcase
case "c": inc tVal, 12 : endcase
case "d": inc tVal, 13 : endcase
case "e": inc tVal, 14 : endcase
case "f": inc tVal, 15 : endcase
endselect
endfunction tVal
function ParseColor(rValue as string)
res as integer
valFirstChar as string
valR as string
valG as string
valB as string
valLen as integer
valFirstChar = left(rValue, 1)
valLen = len(rValue)
if valFirstChar = "#"
if valLen = 4
//short form hex #000
valR = mid(rValue, 2, 1)
valG = mid(rValue, 3, 1)
valB = mid(rValue, 4, 1)
res = MakeColor(HexToByte(valR + valR), HexToByte(valG + valG), HexToByte(valB + valB))
else
if valLen = 7
//rgb hex #000000
valR = mid(rValue, 2, 2)
valG = mid(rValue, 4, 2)
valB = mid(rValue, 6, 2)
res = MakeColor(HexToByte(valR), HexToByte(valG), HexToByte(valB))
else
//argb hex #ff000000
valR = mid(rValue, 4, 2)
valG = mid(rValue, 6, 2)
valB = mid(rValue, 8, 2)
res = MakeColor(HexToByte(valR), HexToByte(valG), HexToByte(valB))
endif
endif
else
if valFirstChar = "r"
//rgb cast rgb(0, 0, 0)
rValue = mid(rValue, 5, valLen - 5)
valR = getStringToken2(rValue, ",", 1)
valG = getStringToken2(rValue, ",", 2)
valB = getStringToken2(rValue, ",", 3)
res = MakeColor(val(valR), val(valG), val(valB))
endif
endif
endfunction res
function WrapValue(rMin, rMax, rVal)
while rVal > rMax
rVal = rVal - rMax - 1
endwhile
while rVal < rMin
rVal = rMax + rVal + 1
endwhile
endfunction rVal
I do need some things like get/set bit though, so I’ll start new classes for Data and for Hardware.
Common.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AGKCore
{
public static class Data
{
public static int GetBit(int rBit, int rData)
{
return Convert.ToInt32((rData & (1 << (rBit - 1))) != 0);
}
public static int SetBit(int rBit, int rData, int rVal)
{
if(rVal > 0)
{
rData = rData | (1 << (rBit - 1));
}
else if((rData & (1 << (rBit - 1))) != 0)
{
rData = ~(rData & (1 << (rBit - 1)));
}
return rData;
}
}
}
Bit operators a slightly different in C# than in agk or dbpro, so I will drop a quick test into Program.cs to verify that they are working correctly:
Console.WriteLine("0 : bit 1:" + Data.GetBit(1, 0));
Console.WriteLine("1 : bit 1:" + Data.GetBit(1, 1));
Console.WriteLine("2 : bit 1:" + Data.GetBit(1, 2));
Console.WriteLine("3 : bit 1:" + Data.GetBit(1, 3));
Console.WriteLine("4 : bit 1:" + Data.GetBit(1, 4));
Console.WriteLine("0 : bit 2:" + Data.GetBit(2, 0));
Console.WriteLine("1 : bit 2:" + Data.GetBit(2, 1));
Console.WriteLine("2 : bit 2:" + Data.GetBit(2, 2));
Console.WriteLine("3 : bit 2:" + Data.GetBit(2, 3));
Console.WriteLine("4 : bit 2:" + Data.GetBit(2, 4));
int testVal = 0;
testVal = Data.SetBit(1, testVal, 1);
testVal = Data.SetBit(3, testVal, 1);
Console.WriteLine(testVal.ToString() + " : bit 1:" + Data.GetBit(1, testVal));
Console.WriteLine(testVal.ToString() + " : bit 2:" + Data.GetBit(2, testVal));
Console.WriteLine(testVal.ToString() + " : bit 3:" + Data.GetBit(3, testVal));
Console.WriteLine(testVal.ToString() + " : bit 4:" + Data.GetBit(4, testVal));
-- The output --
0 : bit 1:0
1 : bit 1:1
2 : bit 1:0
3 : bit 1:1
4 : bit 1:0
0 : bit 2:0
1 : bit 2:0
2 : bit 2:1
3 : bit 2:1
4 : bit 2:0
5 : bit 1:1
5 : bit 2:0
5 : bit 3:1
5 : bit 4:0
Looks good. I’ll skip the rest of Common for now and move on to hardware inputs. I’m going to move the input handlers from Core.cs to Hardware.cs
This will leave Core.cs to handle the underlying window/form events while Hardware.cs will handle input devices.
Now in agk/dbpro we have to roll our own “event handlers” we can detect a button’s current state, but we have to build handling to determine a new press from a hold, a new release from a not being pressed. And to do this we have to poll key states every loop.
Now in C# with the form event handlers, we don’t have to constantly poll the state of every key and can easily detect a new press/new release when the event fires… for the mouse at least. Unfortunately holding a keyboard key is firing multiple onKeyDown events, so I still need to handle detection of a new press vs a held press.
Hardware.cs
using AgkSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AGKCore
{
public static class Hardware
{
public struct MouseInput
{
public float MoveX;
public float MoveY;
public float MoveZ;
public int PosX;
public int PosY;
}
public static MouseInput Mouse = new MouseInput();
public static int[] Input = new int[255]; //bit 1 = isDown, bit 2 = wasDown
public static uint MouseEnum(int rBtn)
{
switch (rBtn)
{
case 1048576:
return 1; //Left
case 2097152:
return 2; //Right
case 4194304:
return 4; //Middle
default:
return 0;
}
}
public static void OnMouseDown(object sender, MouseEventArgs e)
{
Input[MouseEnum((int)e.Button)] = Data.SetBit(2, Input[MouseEnum((int)e.Button)], Data.GetBit(1, Input[MouseEnum((int)e.Button)]));
Input[MouseEnum((int)e.Button)] = Data.SetBit(1, Input[MouseEnum((int)e.Button)], 1);
}
public static void OnMouseUp(object sender, MouseEventArgs e)
{
Input[MouseEnum((int)e.Button)] = Data.SetBit(2, Input[MouseEnum((int)e.Button)], 0);
Input[MouseEnum((int)e.Button)] = Data.SetBit(1, Input[MouseEnum((int)e.Button)], 0);
}
public static void OnMouseMove(object sender, MouseEventArgs e)
{
Mouse.MoveX = e.X;
Mouse.MoveY = e.Y;
}
public static void OnMouseWheel(object sender, MouseEventArgs e)
{
Mouse.MoveZ = e.Delta;
}
public static void OnKeyDown(object sender, KeyEventArgs e)
{
Input[e.KeyValue] = Data.SetBit(2, Input[e.KeyValue], Data.GetBit(1, Input[e.KeyValue]));
Input[e.KeyValue] = Data.SetBit(1, Input[e.KeyValue], 1);
}
public static void OnKeyUp(object sender, KeyEventArgs e)
{
Input[e.KeyValue] = Data.SetBit(2, Input[e.KeyValue], 0);
Input[e.KeyValue] = Data.SetBit(1, Input[e.KeyValue], 0);
}
}
}
And leaves us with Core.cs:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using AgkSharp;
namespace AGKCore
{
public static class App
{
static Form m_Window;
static bool m_bMaximized = false;
public static bool DisableEscape { get; set; } = false;
public static AppConfig Config;
public static AppStatus Status;
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll", SetLastError = true)]
static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetKeyboardLayout(uint idThread);
[DllImport("user32.dll", SetLastError = true)]
static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public static Form CreateWindow(string title, int width, int height, bool fullscreen) => CreateWin32Window(title, width, height, fullscreen);
public static Form CreateWin32Window(string title, int width, int height, bool fullscreen)
{
m_Window = new Form
{
Text = title,
ClientSize = new Size(width, height),
StartPosition = FormStartPosition.CenterScreen,
};
if (fullscreen)
{
m_Window.WindowState = FormWindowState.Normal;
m_Window.FormBorderStyle = FormBorderStyle.None;
m_Window.Bounds = Screen.PrimaryScreen.Bounds;
}
if (System.IO.File.Exists("icon.ico"))
m_Window.Icon = new Icon("icon.ico");
m_Window.MouseDown += Hardware.OnMouseDown;
m_Window.MouseUp += Hardware.OnMouseUp;
m_Window.MouseWheel += Hardware.OnMouseWheel;
m_Window.MouseMove += Hardware.OnMouseMove;
m_Window.KeyDown += Hardware.OnKeyDown;
m_Window.KeyUp += Hardware.OnKeyUp;
m_Window.SizeChanged += Core_OnSizeChanged;
m_Window.Move += Core_OnMove;
m_Window.Activated += Core_OnActivated;
m_Window.Deactivate += Core_OnDeactivate;
m_Window.GotFocus += Core_OnGotFocus;
m_Window.LostFocus += Core_OnLostFocus;
m_Window.FormClosing += Core_OnClose;
m_Window.Show();
return m_Window;
}
static void Core_OnClose(object sender, EventArgs e)
{
App.Status.IsRunning = false;
}
static void Core_OnSizeChanged(object sender, EventArgs e)
{
m_bMaximized = m_Window.WindowState == FormWindowState.Maximized;
m_Window.TopMost = m_bMaximized;
m_Window.Update();
Agk.UpdateDeviceSize();
Agk.WindowMoved();
}
static void Core_OnMove(object sender, EventArgs e)
{
Agk.WindowMoved();
}
static void Core_OnDeactivate(object sender, EventArgs e)
{
Agk.MouseLeftButton(0, 0);
Agk.MouseRightButton(0, 0);
Agk.MouseMiddleButton(0, 0);
m_Window.TopMost = m_bMaximized;
m_Window.Update();
Agk.Paused();
Agk.WindowMoved();
}
static void Core_OnActivated(object sender, EventArgs e)
{
m_Window.TopMost = m_bMaximized;
m_Window.Update();
Agk.Resumed();
Agk.WindowMoved();
}
static void Core_OnGotFocus(object sender, EventArgs e)
{
Agk.WindowMoved();
}
static void Core_OnLostFocus(object sender, EventArgs e)
{
Agk.WindowMoved();
}
public static bool InitAGK(Form window)
{
if (window.Handle != null)
Agk.InitGL(window.Handle);
return true;
}
public static bool InitAGK()
{
if (m_Window.Handle != null)
Agk.InitGL(m_Window.Handle);
return true;
}
public static bool LoopAGK()
{
Application.DoEvents();
if (Agk.IsCapturingImage() > 0)
System.Threading.Thread.Sleep(10);
return App.Status.IsRunning;
}
public static void CleanUp()
{
Agk.CleanUp();
}
public static bool Init(string[] args, string title)
{
App.Status.LoadState = 1;
App.Status.LoadStage = 1;
//config defaults
App.Config.Log.Level = 3;
App.Config.Log.File = System.AppDomain.CurrentDomain.BaseDirectory + "app.log";
App.Config.Log.Channels = "*";
App.Config.Screen.Fullscreen = true;
App.Config.Screen.Width = 0;
App.Config.Screen.Height = 0;
App.Config.Screen.Vsync = true;
//config overrides
foreach (string arg in args)
{
switch (arg.Split('=').First().ToLower())
{
case "sw": App.Config.Screen.Width = Convert.ToInt32(arg.Split('=').Last()); break;
case "sh": App.Config.Screen.Height = Convert.ToInt32(arg.Split('=').Last()); break;
case "fs": App.Config.Screen.Fullscreen = Convert.ToBoolean(arg.Split('=').Last()); break;
case "vsync": App.Config.Screen.Vsync = Convert.ToBoolean(arg.Split('=').Last()); break;
case "log": App.Config.Log.Level = Convert.ToInt32(arg.Split('=').Last()); break;
default: break;
}
}
//init the screen
if (App.Config.Screen.Width == 0)
{
App.Config.Screen.Width = System.Windows.Forms.SystemInformation.PrimaryMonitorSize.Width;
App.Config.Screen.Height = System.Windows.Forms.SystemInformation.PrimaryMonitorSize.Height;
}
App.Config.Screen.CenterX = (int)Math.Floor(App.Config.Screen.Width * 0.5);
App.Config.Screen.CenterY = (int)Math.Floor(App.Config.Screen.Height * 0.5);
if (App.Config.Screen.Width > 1280)
{
App.Config.Screen.Layout = 1;
}
else {
App.Config.Screen.Layout = 2;
}
App.CreateWindow(title, App.Config.Screen.Width, App.Config.Screen.Height, App.Config.Screen.Fullscreen);
if (!App.InitAGK())
{
return false;
}
Agk.SetResolutionMode(1);
Agk.SetOrientationAllowed(0, 0, 1, 0);
if (!App.Config.Screen.Fullscreen)
{
Agk.SetWindowAllowResize(0);
}
Agk.SetVirtualResolution(App.Config.Screen.Width, App.Config.Screen.Height);
Agk.SetScissor(0, 0, 0, 0);
Agk.SetVSync(App.Config.Screen.Vsync);
if (!App.Config.Screen.Vsync)
{
Agk.SetSyncRate(0, 0);
}
//init AGK Misc
Agk.SetPrintSize(16.0f);
Agk.SetPrintColor(255, 255, 255);
Agk.SetClearColor(0, 0, 0);
Agk.UseNewDefaultFonts(1);
//init log
System.IO.File.WriteAllText(App.Config.Log.File, "Timestamp | File | Level | Channel | Log" + Environment.NewLine);
System.IO.File.AppendAllText(App.Config.Log.File, "==================================================================" + Environment.NewLine);
App.Status.IsRunning = true;
return true;
}
public static void Log(string rSource, int rLevel, string rChannel, string rContent)
{
if(rLevel >= App.Config.Log.Level)
{
if(App.Config.Log.Channels == "*" || App.Config.Log.Channels.Contains("|" + rChannel + "|"))
{
System.IO.File.AppendAllText(App.Config.Log.File, DateTime.Now.ToString("HH:mm:ss.fff") + " | " + rSource.PadRight(15) + " | " + rLevel.ToString().PadRight(5) + " | " + rChannel.PadRight(10) + " | " + rContent + Environment.NewLine);
}
}
}
}
public struct AppConfig
{
public LogConfig Log;
public ScreenConfig Screen;
}
public struct ScreenConfig
{
public int Width;
public int Height;
public int X1;
public int Y1;
public int X2;
public int Y2;
public int CenterX;
public int CenterY;
public int Layout;
public bool Fullscreen;
public bool Vsync;
}
public struct LogConfig
{
public int Level;
public string Channels;
public string File;
}
public struct AppStatus
{
public bool IsRunning;
public int LoadState; //0 not loaded | 1 loading | 2 title loop (UI only) | 3 level load finished | 4 game in progress | 5 level reload/transition
public int LoadStage; //0 init resources | 1 transition in | 2 body | 3 transition out
public int LoadType; //0 not loading | 1 title load | 2 level load
}
}
And Program.cs can give us some quick testing.
using System;
using AgkSharp;
using AGKCore;
using System.Reflection;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
namespace AgkSharp_Template
{
static class Program
{
[STAThread]
static void Main(string[] args)
{
var attrs = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false).FirstOrDefault() as AssemblyTitleAttribute;
if(!App.Init(args, attrs.Title))
{
return;
}
#if DEBUG
App.Log("Program.cs", 3, "main", "> Init Complete");
#endif
// display a background and center it
uint bgImg = Agk.CreateSprite(Agk.LoadImage("media/background.jpg"));
Agk.SetSpritePosition(bgImg, App.Config.Screen.CenterX - 160, App.Config.Screen.CenterY - 240);
// create a sprite and center it
Agk.CreateSprite(1, 0);
Agk.SetSpritePosition(1, App.Config.Screen.CenterX - 30, App.Config.Screen.CenterY - 44);
// add individual images into an animation list
Agk.AddSpriteAnimationFrame(1, Agk.LoadImage("media/item0.png"));
Agk.AddSpriteAnimationFrame(1, Agk.LoadImage("media/item1.png"));
Agk.AddSpriteAnimationFrame(1, Agk.LoadImage("media/item2.png"));
Agk.AddSpriteAnimationFrame(1, Agk.LoadImage("media/item3.png"));
Agk.AddSpriteAnimationFrame(1, Agk.LoadImage("media/item4.png"));
// play the sprite at 10 fps, looping, going from frame 1 to 5
Agk.PlaySprite(1, 10.0f, 1, 1, 5);
while (App.LoopAGK())
{
#if DEBUG
App.Log("Program.cs", 1, "main", "--- Begin main loop ---");
#endif
Agk.Print(Agk.ScreenFPS());
Agk.Print("A is down: " + Data.GetBit(1, Hardware.Input[(int)System.Windows.Forms.Keys.A]));
Agk.Print("A was down: " + Data.GetBit(2, Hardware.Input[(int)System.Windows.Forms.Keys.A]));
Agk.Print("Left mouse is down: " + Data.GetBit(1, Hardware.Input[Hardware.MouseEnum((int)MouseButtons.Left)]));
Agk.Print("Left mouse was down: " + Data.GetBit(2, Hardware.Input[Hardware.MouseEnum((int)MouseButtons.Left)]));
Agk.Sync();
}
App.CleanUp();
}
}
}
I can tell this is going to need some further work, but it's at a decent spot to move forward with for now.
Repo is updated.
http://games.joshkirklin.com/sulium
A single player RPG featuring a branching, player driven storyline of meaningful choices and multiple endings alongside challenging active combat and intelligent AI.