updated fighting game sample: better camera bounds, added combos

This commit is contained in:
turanszkij
2019-05-13 19:25:45 +01:00
parent 53e7f6ca3b
commit 51440d3076
8 changed files with 199 additions and 87 deletions
@@ -430,6 +430,11 @@ Describes an orientation in 3D space.
- UpdateCamera() -- update the camera matrices
- TransformCamera(TransformComponent transform) -- copies the transform's orientation to the camera. Camera matrices are not updated immediately. They will be updated by the Scene::Update() (if the camera is part of the scene), or by manually calling UpdateCamera()
- GetFOV() : float result
- SetFOV(float value)
- GetNearPlane() : float result
- SetNearPlane(float value)
- GetFarPlane() : float result
- SetFarPlane(float value)
- GetView() : Matrix result
- GetProjection() : Matrix result
- GetViewProjection() : Matrix result
+3 -3
View File
@@ -43,7 +43,7 @@ CameraWindow::CameraWindow(wiGUI* gui) :GUI(gui)
Scene& scene = wiSceneSystem::GetScene();
CameraComponent& camera = wiRenderer::GetCamera();
camera.zFarP = args.fValue;
camera.UpdateProjection();
camera.UpdateCamera();
});
cameraWindow->AddWidget(farPlaneSlider);
@@ -55,7 +55,7 @@ CameraWindow::CameraWindow(wiGUI* gui) :GUI(gui)
Scene& scene = wiSceneSystem::GetScene();
CameraComponent& camera = wiRenderer::GetCamera();
camera.zNearP = args.fValue;
camera.UpdateProjection();
camera.UpdateCamera();
});
cameraWindow->AddWidget(nearPlaneSlider);
@@ -66,7 +66,7 @@ CameraWindow::CameraWindow(wiGUI* gui) :GUI(gui)
Scene& scene = wiSceneSystem::GetScene();
CameraComponent& camera = wiRenderer::GetCamera();
camera.fov = args.fValue / 180.f * XM_PI;
camera.UpdateProjection();
camera.UpdateCamera();
});
cameraWindow->AddWidget(fovSlider);
+2 -5
View File
@@ -928,15 +928,12 @@ namespace wiSceneSystem
height = newHeight;
fov = newFOV;
UpdateProjection();
UpdateCamera();
}
void CameraComponent::UpdateProjection()
{
XMStoreFloat4x4(&Projection, XMMatrixPerspectiveFovLH(fov, width / height, zFarP, zNearP)); // reverse zbuffer!
}
void CameraComponent::UpdateCamera()
{
XMStoreFloat4x4(&Projection, XMMatrixPerspectiveFovLH(fov, width / height, zFarP, zNearP)); // reverse zbuffer!
XMVECTOR _Eye = XMLoadFloat3(&Eye);
XMVECTOR _At = XMLoadFloat3(&At);
XMVECTOR _Up = XMLoadFloat3(&Up);
-1
View File
@@ -732,7 +732,6 @@ namespace wiSceneSystem
XMFLOAT4X4 InvView, InvProjection, InvVP;
void CreatePerspective(float newWidth, float newHeight, float newNear, float newFar, float newFOV = XM_PI / 3.0f);
void UpdateProjection();
void UpdateCamera();
void TransformCamera(const TransformComponent& transform);
void Reflect(const XMFLOAT4& plane = XMFLOAT4(0, 1, 0, 0));
+54
View File
@@ -1174,6 +1174,11 @@ Luna<CameraComponent_BindLua>::FunctionType CameraComponent_BindLua::methods[] =
lunamethod(CameraComponent_BindLua, UpdateCamera),
lunamethod(CameraComponent_BindLua, TransformCamera),
lunamethod(CameraComponent_BindLua, GetFOV),
lunamethod(CameraComponent_BindLua, SetFOV),
lunamethod(CameraComponent_BindLua, GetNearPlane),
lunamethod(CameraComponent_BindLua, SetNearPlane),
lunamethod(CameraComponent_BindLua, GetFarPlane),
lunamethod(CameraComponent_BindLua, SetFarPlane),
lunamethod(CameraComponent_BindLua, GetView),
lunamethod(CameraComponent_BindLua, GetProjection),
lunamethod(CameraComponent_BindLua, GetViewProjection),
@@ -1231,6 +1236,55 @@ int CameraComponent_BindLua::GetFOV(lua_State* L)
wiLua::SSetFloat(L, component->fov);
return 1;
}
int CameraComponent_BindLua::SetFOV(lua_State* L)
{
int argc = wiLua::SGetArgCount(L);
if (argc > 0)
{
component->fov = wiLua::SGetFloat(L, 1);
}
else
{
wiLua::SError(L, "SetFOV(float value) not enough arguments!");
}
return 0;
}
int CameraComponent_BindLua::GetNearPlane(lua_State* L)
{
wiLua::SSetFloat(L, component->zNearP);
return 1;
}
int CameraComponent_BindLua::SetNearPlane(lua_State* L)
{
int argc = wiLua::SGetArgCount(L);
if (argc > 0)
{
component->zNearP = wiLua::SGetFloat(L, 1);
}
else
{
wiLua::SError(L, "SetNearPlane(float value) not enough arguments!");
}
return 0;
}
int CameraComponent_BindLua::GetFarPlane(lua_State* L)
{
wiLua::SSetFloat(L, component->zFarP);
return 1;
}
int CameraComponent_BindLua::SetFarPlane(lua_State* L)
{
int argc = wiLua::SGetArgCount(L);
if (argc > 0)
{
component->zFarP = wiLua::SGetFloat(L, 1);
}
else
{
wiLua::SError(L, "SetFarPlane(float value) not enough arguments!");
}
return 0;
}
int CameraComponent_BindLua::GetView(lua_State* L)
{
Luna<Matrix_BindLua>::push(L, new Matrix_BindLua(component->GetView()));
+5
View File
@@ -147,6 +147,11 @@ namespace wiSceneSystem_BindLua
int UpdateCamera(lua_State* L);
int TransformCamera(lua_State* L);
int GetFOV(lua_State* L);
int SetFOV(lua_State* L);
int GetNearPlane(lua_State* L);
int SetNearPlane(lua_State* L);
int GetFarPlane(lua_State* L);
int SetFarPlane(lua_State* L);
int GetView(lua_State* L);
int GetProjection(lua_State* L);
int GetViewProjection(lua_State* L);
+1 -1
View File
@@ -9,7 +9,7 @@ namespace wiVersion
// minor features, major updates
const int minor = 26;
// minor bug fixes, alterations, refactors, updates
const int revision = 23;
const int revision = 24;
long GetVersion()
+129 -77
View File
@@ -20,7 +20,7 @@
-- "2684" or "2369874"...
-- The require_input("inputstring") facility will help detect instant input execution
-- The require_input_window("inputstring", allowed_latency_window) facility can detect inputs that are executed over multiple frames
-- Neutral motion is "5", that is not necessary to put into input strings in most cases, but it can help, for example: double tap right button would need a neutral in between the two presses, like this: 656
-- Neutral motion is "5", that can help to specify in some cases, for example: double tap right button would need a neutral in between the two presses, like this: 656. Also, "5A" means that A is pressed only once, not continuously.
local scene = GetScene()
@@ -54,13 +54,13 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
hurtboxes = {}, -- list of AABBs that the opponent can hit with a hitbox
hitboxes = {}, -- list of AABBs that can hit the opponent's hurtboxes
guardboxes = {}, -- list of AABBs that can indicate to the opponent that guarding can be started
hitconfirm = false, -- will be true in this frame if this player hit the opponent
hitconfirms = {}, -- contains the frames in which the opponent was hit. Resets in the beginning of every action
hitconfirms_guard = {}, -- contains the frames in which the opponent was hit but guarded. Resets in the beginning of every action
hurt = false, -- will be true in a frame if this player was hit by the opponent
jumps_remaining = 2, -- for double jump
push = Vector(), -- will affect opponent's velocity
can_guard = false, -- true when player is inside opponent's guard box and can initiate guarding state
guarding = false, -- if true, player can't be hit
hit_guard = false, -- true when opponent is guarding the attack
max_hp = 10000, -- maximum health
hp = 10000, -- current health
fireball_active = false, -- fireball is on screen or not
@@ -80,7 +80,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
local emitter_entity = INVALID_ENTITY
local burst_count = 0
local spark_color = Vector()
if(self.hit_guard) then
if(self:require_hitconfirm_guard()) then
emitter_entity = self.effect_guard
burst_count = 4
spark_color = Vector(0,0.5,1,1)
@@ -110,7 +110,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
light_component.SetType(POINT)
light_component.SetRange(8)
light_component.SetEnergy(4)
if(self.hit_guard) then
if(self:require_hitconfirm_guard()) then
light_component.SetColor(Vector(0,0.5,1)) -- guarded attack emits blueish light
else
light_component.SetColor(Vector(1,0.5,0)) -- successful attack emits orangeish light
@@ -180,7 +180,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
return false -- match failure
end,
require_input = function(self, inputString) -- player input notation (immediate) (help: see readme on top of this file)
return self:require_input_window(inputString, 0)
return self:require_input_window(inputString, string.len(inputString))
end,
require_frame = function(self, frame) -- specific frame
return self.frame == frame
@@ -191,11 +191,23 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
require_animationfinish = function(self) -- animation is finished
return scene.Component_GetAnimation(self.states[self.state].anim).IsEnded()
end,
require_hitconfirm = function(self) -- true if this player hit the other
return self.hitconfirm
require_hitconfirm = function(self, frame) -- true if this player hit the other, optionally provide frame number to check if hit occured in the past or not
frame = frame or (self.frame - 1)
for i,hit in ipairs(self.hitconfirms) do
if(hit == frame) then
return true
end
end
return false
end,
require_hitconfirm_guard = function(self) -- true if this player hit the opponent but the opponent guarded it
return self.hitconfirm and self.hit_guard
require_hitconfirm_guard = function(self, frame) -- true if this player hit the opponent but the opponent guarded it, optionally provide frame number to check if hit occured in the past or not
frame = frame or (self.frame - 1)
for i,hit in ipairs(self.hitconfirms_guard) do
if(hit == frame) then
return true
end
end
return false
end,
require_hurt = function(self) -- true if this player was hit by the other
return self.hurt
@@ -439,7 +451,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
hurtbox = AABB(Vector(-1.2), Vector(1.2, 5.5)),
guardbox = AABB(Vector(-2,0),Vector(6,8)),
update_collision = function(self)
if(self:require_window(3,6)) then
if(self:require_frame(5)) then
self:set_box_local(self.hitboxes, AABB(Vector(0.5,2), Vector(3,5)) )
end
end,
@@ -515,7 +527,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
hurtbox = AABB(Vector(-1.2), Vector(1.2, 5.5)),
guardbox = AABB(Vector(-2,0),Vector(6,8)),
update_collision = function(self)
if(self:require_window(6,8)) then
if(self:require_window(7,8)) then
self:set_box_local(self.hitboxes, AABB(Vector(0,0), Vector(3,3)) )
end
end,
@@ -534,7 +546,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
hurtbox = AABB(Vector(-1.2), Vector(1.2, 5.5)),
guardbox = AABB(Vector(-2,0),Vector(6,8)),
update_collision = function(self)
if(self:require_window(8,13)) then
if(self:require_window(10,13)) then
self:set_box_local(self.hitboxes, AABB(Vector(0,0), Vector(4,3)) )
end
end,
@@ -646,7 +658,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
if(self:require_hitconfirm_guard()) then
self.push = Vector(0.1 * self.face, 0) -- if guarded, don't push opponent up
else
self.push = Vector(0.1 * self.face, 0.5) -- if not guarded, push opponent up
self.push = Vector(0.1 * self.face, 0.6) -- if not guarded, push opponent up
end
end
end,
@@ -854,11 +866,11 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
{ "JumpForward", condition = function(self) return self:require_input("9") end, },
{ "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, },
{ "ChargeKick", condition = function(self) return self:require_input_window("4444444444444444446C", 30) or self:require_input_window("1111111111111111116C", 30) end, },
{ "Taunt", condition = function(self) return self:require_input_window("T", 10) end, },
{ "LightPunch", condition = function(self) return self:require_input("A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("B") end, },
{ "LightKick", condition = function(self) return self:require_input("C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("D") end, },
{ "Taunt", condition = function(self) return self:require_input_window("5T", 10) end, },
{ "LightPunch", condition = function(self) return self:require_input("5A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("5B") end, },
{ "LightKick", condition = function(self) return self:require_input("5C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("5D") end, },
},
Walk_Backward = {
{ "Guard", condition = function(self) return self:require_guard() and self:require_input("4") end, },
@@ -870,10 +882,10 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
{ "JumpBack", condition = function(self) return self:require_input("7") end, },
{ "Idle", condition = function(self) return self:require_input("5") end, },
{ "ChargeKick", condition = function(self) return self:require_input_window("4444444444444444446C", 30) or self:require_input_window("1111111111111111116C", 30) end, },
{ "LightPunch", condition = function(self) return self:require_input("A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("B") end, },
{ "LightKick", condition = function(self) return self:require_input("C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("D") end, },
{ "LightPunch", condition = function(self) return self:require_input("5A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("5B") end, },
{ "LightKick", condition = function(self) return self:require_input("5C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("5D") end, },
{ "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, },
},
Walk_Forward = {
@@ -888,9 +900,9 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
{ "Idle", condition = function(self) return self:require_input("5") end, },
{ "ChargeKick", condition = function(self) return self:require_input_window("4444444444444444446C", 30) or self:require_input_window("1111111111111111116C", 30) end, },
{ "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("B") end, },
{ "LightKick", condition = function(self) return self:require_input("C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("D") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("5B") end, },
{ "LightKick", condition = function(self) return self:require_input("5C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("5D") end, },
},
Dash_Backward = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
@@ -914,9 +926,9 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
{ "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, },
{ "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, },
{ "LightPunch", condition = function(self) return self:require_input("5A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("B") end, },
{ "LightKick", condition = function(self) return self:require_input("C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("D") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("5B") end, },
{ "LightKick", condition = function(self) return self:require_input("5C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("5D") end, },
},
RunEnd = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
@@ -934,10 +946,10 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
{ "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, },
{ "ChargeKick", condition = function(self) return self:require_input_window("4444444444444444446C", 30) or self:require_input_window("1111111111111111116C", 30) end, },
{ "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, },
{ "LightPunch", condition = function(self) return self:require_input("A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("B") end, },
{ "LightKick", condition = function(self) return self:require_input("C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("D") end, },
{ "LightPunch", condition = function(self) return self:require_input("5A") end, },
{ "HeavyPunch", condition = function(self) return self:require_input("5B") end, },
{ "LightKick", condition = function(self) return self:require_input("5C") end, },
{ "HeavyKick", condition = function(self) return self:require_input("5D") end, },
},
Jump = {
{ "StaggerAirStart", condition = function(self) return self:require_hurt() and self.position.GetY() > 0 end, },
@@ -1029,6 +1041,8 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
LightPunch = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
{ "Idle", condition = function(self) return self:require_animationfinish() end, },
{ "LightPunch", condition = function(self) return self:require_frame(11) and self:require_hitconfirm(5) and self:require_input_window("5A", 10) end, },
{ "HeavyPunch", condition = function(self) return self:require_frame(11) and self:require_hitconfirm(5) and self:require_input_window("5B", 10) end, },
},
ForwardLightPunch = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
@@ -1037,6 +1051,9 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
HeavyPunch = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
{ "Idle", condition = function(self) return self:require_animationfinish() end, },
{ "ForwardLightPunch", condition = function(self) return self:require_frame(11) and self:require_hitconfirm(6) and self:require_input_window("6A", 10) end, },
{ "LightKick", condition = function(self) return self:require_frame(11) and self:require_hitconfirm(6) and self:require_input_window("5C", 10) end, },
{ "HeavyKick", condition = function(self) return self:require_frame(11) and self:require_hitconfirm(6) and self:require_input_window("5D", 10) end, },
},
LowPunch = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
@@ -1045,6 +1062,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
LightKick = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
{ "Idle", condition = function(self) return self:require_animationfinish() end, },
{ "HeavyKick", condition = function(self) return self:require_frame(11) and self:require_hitconfirm(8) and self:require_input_window("5D", 10) end, },
},
HeavyKick = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
@@ -1069,6 +1087,9 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
Uppercut = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
{ "Idle", condition = function(self) return self:require_animationfinish() end, },
{ "Jump", condition = function(self) return self:require_frame(10) and self:require_hitconfirm(4) and self:require_input_window("8", 20) end, },
{ "JumpBack", condition = function(self) return self:require_frame(10) and self:require_hitconfirm(4) and self:require_input_window("7", 20) end, },
{ "JumpForward", condition = function(self) return self:require_frame(10) and self:require_hitconfirm(4) and self:require_input_window("9", 20) end, },
},
Jaunt = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
@@ -1124,11 +1145,11 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
},
StaggerAir = {
{ "StaggerAirStart", condition = function(self) return self:require_hurt() end, },
{ "StaggerAirEnd", condition = function(self) return not self:require_hurt() end, },
{ "StaggerAirEnd", condition = function(self) return not self:require_hurt() and self.position.GetY() < 3 end, },
},
StaggerAirEnd = {
{ "StaggerAirStart", condition = function(self) return self:require_hurt() end, },
{ "Downed", condition = function(self) return self:require_animationfinish() and self.position.GetY() < 0.2 end, },
{ "Downed", condition = function(self) return self:require_animationfinish() end, },
},
Downed = {
@@ -1146,6 +1167,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
Die = {
},
Getup = {
{ "StaggerStart", condition = function(self) return self:require_hurt() end, },
{ "Idle", condition = function(self) return self:require_animationfinish() end, },
},
},
@@ -1162,9 +1184,14 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
scene.Component_GetAnimation(self.states[dst_state].anim).Play()
self.frame = 0
self.state = dst_state
self.hitconfirms = {}
self.hitconfirms_guard = {}
end,
-- Step state machine and execute current state:
ExecuteStateMachine = function(self)
self.frame = self.frame + 1
self.guarding = false
-- Parse state machine at current state and perform transition if applicable:
local transition_candidates = self.statemachine[self.state]
if(transition_candidates ~= nil) then
@@ -1378,15 +1405,15 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
AI = function(self)
-- todo some better AI bot behaviour
if(self.ai_state == "Jump") then
table.insert(self.input_buffer, {age = 0, command = "8"})
table.insert(self.input_buffer, {age = 0, command = '8'})
elseif(self.ai_state == "Crouch") then
table.insert(self.input_buffer, {age = 0, command = "2"})
table.insert(self.input_buffer, {age = 0, command = '2'})
elseif(self.ai_state == "Guard" and self:require_guard()) then
table.insert(self.input_buffer, {age = 0, command = "4"})
table.insert(self.input_buffer, {age = 0, command = '4'})
elseif(self.ai_state == "Attack") then
table.insert(self.input_buffer, {age = 0, command = "A"})
table.insert(self.input_buffer, {age = 0, command = 'A'})
else
table.insert(self.input_buffer, {age = 0, command = "5"})
table.insert(self.input_buffer, {age = 0, command = '5'})
end
end,
@@ -1399,11 +1426,11 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
local right = input.Down(string.byte('D'), INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_RIGHT, INPUT_TYPE_GAMEPAD, playerindex)
local up = input.Down(string.byte('W'), INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_UP, INPUT_TYPE_GAMEPAD, playerindex)
local down = input.Down(string.byte('S'), INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_DOWN, INPUT_TYPE_GAMEPAD, playerindex)
local A = input.Press(VK_RIGHT, INPUT_TYPE_KEYBOARD, playerindex) or input.Press(GAMEPAD_BUTTON_3, INPUT_TYPE_GAMEPAD, playerindex)
local B = input.Press(VK_UP, INPUT_TYPE_KEYBOARD, playerindex) or input.Press(GAMEPAD_BUTTON_4, INPUT_TYPE_GAMEPAD, playerindex)
local C = input.Press(VK_LEFT, INPUT_TYPE_KEYBOARD, playerindex) or input.Press(GAMEPAD_BUTTON_1, INPUT_TYPE_GAMEPAD, playerindex)
local D = input.Press(VK_DOWN, INPUT_TYPE_KEYBOARD, playerindex) or input.Press(GAMEPAD_BUTTON_2, INPUT_TYPE_GAMEPAD, playerindex)
local T = input.Press(string.byte('T'), INPUT_TYPE_KEYBOARD, playerindex) or input.Press(GAMEPAD_BUTTON_5, INPUT_TYPE_GAMEPAD, playerindex)
local A = input.Down(VK_RIGHT, INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_3, INPUT_TYPE_GAMEPAD, playerindex)
local B = input.Down(VK_UP, INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_4, INPUT_TYPE_GAMEPAD, playerindex)
local C = input.Down(VK_LEFT, INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_1, INPUT_TYPE_GAMEPAD, playerindex)
local D = input.Down(VK_DOWN, INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_2, INPUT_TYPE_GAMEPAD, playerindex)
local T = input.Down(string.byte('T'), INPUT_TYPE_KEYBOARD, playerindex) or input.Down(GAMEPAD_BUTTON_5, INPUT_TYPE_GAMEPAD, playerindex)
-- swap left and right if facing the opposite side:
if(self.face < 0) then
@@ -1428,7 +1455,7 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
table.insert(self.input_buffer, {age = 0, command = '4'})
elseif(right) then
table.insert(self.input_buffer, {age = 0, command = '6'})
else
elseif(not A and not B and not C and not D and not T) then
table.insert(self.input_buffer, {age = 0, command = '5'})
end
@@ -1452,9 +1479,6 @@ local function Character(face, skin_color, shirt_color, hair_color, shoe_color)
-- Update character state and forces once per frame:
Update = function(self)
self.frame = self.frame + 1
self.guarding = false
-- force from gravity:
self.force = Vector(0,-0.04,0)
@@ -1609,14 +1633,13 @@ end
-- script camera state:
local camera_position = Vector()
local camera_position = Vector(0,6,-25)
local camera_transform = TransformComponent()
local CAMERA_HEIGHT = 4 -- camera height from ground
local DEFAULT_CAMERADISTANCE = -9.5 -- the default camera distance when characters are close to each other
local MODIFIED_CAMERADISTANCE = -11.5 -- if the two players are far enough from each other, the camera will zoom out to this distance
local CAMERA_DISTANCE_MODIFIER = 10 -- the required distance between the characters when the camera should zoom out
local XBOUNDS = 20 -- play area horizontal bounds
local CAMERA_SIDE_LENGTH = 10 -- play area inside the camera (character can't move outside camera even if inside the play area)
local PLAYAREA = 32 -- play area horizontal bounds
-- ***Interaction between two characters:
local ResolveCharacters = function(player1, player2)
@@ -1639,25 +1662,37 @@ local ResolveCharacters = function(player1, player2)
player1.request_face = -1
player2.request_face = 1
end
-- Update the system global camera with current values:
camera_transform.ClearTransform()
camera_transform.Translate(camera_position)
camera_transform.UpdateTransform()
local camera = GetCamera()
camera.TransformCamera(camera_transform)
camera.UpdateCamera()
-- Camera bounds:
local camera_side_left = camera_position.GetX() - CAMERA_SIDE_LENGTH
local camera_side_right = camera_position.GetX() + CAMERA_SIDE_LENGTH
local projection_matrix = camera.GetViewProjection()
local z = vector.TransformCoord(Vector(), projection_matrix).GetZ()
local unprojection_matrix = camera.GetInvViewProjection()
local camera_side_left = vector.TransformCoord(Vector(-1,0,z,1), unprojection_matrix).GetX()
local camera_side_right = vector.TransformCoord(Vector(1,0,z,1), unprojection_matrix).GetX()
local camera_halfwidth = (camera_side_right - camera_side_left) * 0.5
-- Push:
-- player on the edge of screen can initiate push transfer:
-- it means that the player cannot be pushed further, so the opponent will be pushed back instead to compensate:
if(player2.position.GetX() <= camera_side_left and player1.push.GetX() < 0) then
if(player2.clipbox.GetMin().GetX() <= camera_side_left and player1.push.GetX() < 0) then
player2.push.SetX(-player1.push.GetX())
end
if(player2.position.GetX() >= camera_side_right and player1.push.GetX() > 0) then
if(player2.clipbox.GetMax().GetX() >= camera_side_right and player1.push.GetX() > 0) then
player2.push.SetX(-player1.push.GetX())
end
if(player1.position.GetX() <= camera_side_left and player2.push.GetX() < 0) then
if(player1.clipbox.GetMin().GetX() <= camera_side_left and player2.push.GetX() < 0) then
player1.push.SetX(-player1.push.GetX())
end
if(player1.position.GetX() >= camera_side_right and player2.push.GetX() > 0) then
if(player1.clipbox.GetMax().GetX() >= camera_side_right and player2.push.GetX() > 0) then
player1.push.SetX(-player1.push.GetX())
end
@@ -1672,6 +1707,12 @@ local ResolveCharacters = function(player1, player2)
-- reset push forces:
player1.push = Vector()
player2.push = Vector()
-- Because hitbox checks are in ccd phase, we will add hitconfirms after ccd phase is over, only once per frame:
local player1_hitconfirm = false
local player1_hitconfirm_guard = false
local player2_hitconfirm = false
local player2_hitconfirm_guard = false
-- Continuous collision detection will be iterated multiple times to avoid "bullet through paper problem":
local iterations = 10
@@ -1682,20 +1723,16 @@ local ResolveCharacters = function(player1, player2)
player2:UpdateCollisionState(ccd_step)
-- Hit/Hurt/Guard:
player1.hitconfirm = false
player1.hurt = false
player1.hit_guard = false
player2.hitconfirm = false
player2.hurt = false
player2.hit_guard = false
-- player1 hits player2:
for i,hitbox in pairs(player1.hitboxes) do
for j,hurtbox in pairs(player2.hurtboxes) do
if(hitbox.Intersects2D(hurtbox)) then
player1.hitconfirm = true
player1_hitconfirm = true
player2.hurt = true
if(player2.guarding) then
player1.hit_guard = true
player1_hitconfirm_guard = true
else
player2.hp = math.max(0, player2.hp - 10)
end
@@ -1707,10 +1744,10 @@ local ResolveCharacters = function(player1, player2)
for i,hitbox in ipairs(player2.hitboxes) do
for j,hurtbox in ipairs(player1.hurtboxes) do
if(hitbox.Intersects2D(hurtbox)) then
player2.hitconfirm = true
player2_hitconfirm = true
player1.hurt = true
if(player1.guarding) then
player2.hit_guard = true
player2_hitconfirm_guard = true
else
player1.hp = math.max(0, player1.hp - 10)
end
@@ -1756,8 +1793,10 @@ local ResolveCharacters = function(player1, player2)
-- Clamp the players inside the camera:
player1.position.SetX(math.clamp(player1.position.GetX(), camera_side_left, camera_side_right))
player2.position.SetX(math.clamp(player2.position.GetX(), camera_side_left, camera_side_right))
player1.position.SetX(player1.position.GetX() + math.saturate(camera_side_left - player1.clipbox.GetMin().GetX()))
player1.position.SetX(player1.position.GetX() - math.saturate(player1.clipbox.GetMax().GetX() - camera_side_right))
player2.position.SetX(player2.position.GetX() + math.saturate(camera_side_left - player2.clipbox.GetMin().GetX()))
player2.position.SetX(player2.position.GetX() - math.saturate(player2.clipbox.GetMax().GetX() - camera_side_right))
local camera_position_new = Vector()
local distanceX = math.abs(player1.position.GetX() - player2.position.GetX())
@@ -1778,7 +1817,7 @@ local ResolveCharacters = function(player1, player2)
end
-- camera horizontal position:
local centerX = math.clamp((player1.position.GetX() + player2.position.GetX()) * 0.5, -XBOUNDS, XBOUNDS)
local centerX = math.clamp((player1.position.GetX() + player2.position.GetX()) * 0.5, -PLAYAREA + camera_halfwidth, PLAYAREA - camera_halfwidth)
camera_position_new.SetX(centerX)
-- smooth camera:
@@ -1786,16 +1825,26 @@ local ResolveCharacters = function(player1, player2)
end
-- Add hitconfirms to the character's own table
if(player1_hitconfirm) then
table.insert(player1.hitconfirms, player1.frame)
end
if(player1_hitconfirm_guard) then
table.insert(player1.hitconfirms_guard, player1.frame)
end
if(player2_hitconfirm) then
table.insert(player2.hitconfirms, player2.frame)
end
if(player2_hitconfirm_guard) then
table.insert(player2.hitconfirms_guard, player2.frame)
end
-- Update collision state once more (but with ccd_step = 0) so that bounding boxes and system transform is up to date:
player1:UpdateCollisionState(0)
player2:UpdateCollisionState(0)
-- Update the system global camera with current values:
camera_transform.ClearTransform()
camera_transform.Translate(camera_position)
camera_transform.UpdateTransform()
GetCamera().TransformCamera(camera_transform)
player1:DebugDraw()
player2:DebugDraw()
@@ -1821,7 +1870,7 @@ runProcess(function()
main.SetActivePath(path)
local help_text = ""
help_text = help_text .. "This script is showcasing how to write a simple fighting game.\n"
help_text = help_text .. "Wicked Engine Fighting game sample script\n"
help_text = help_text .. "\nESCAPE key: quit\nR: reload script"
help_text = help_text .. "\nWASD / Gamepad direction buttons: move"
help_text = help_text .. "\nRight / Gamepad button 1: action A"
@@ -1850,8 +1899,11 @@ runProcess(function()
help_text = help_text .. "\n\t 623B: Shoryuken (forward, then quater circle forward + B)"
help_text = help_text .. "\n\t 236B: Jaunt (quarter circle forward + B)"
help_text = help_text .. "\n\t 236A: Fireball (quarter circle forward + A, also in mid-air)"
help_text = help_text .. "\n\nCombos:"
help_text = help_text .. "\n\t Revolver action: A, B, C, D (Hit action buttons in quick succession)"
help_text = help_text .. "\n\t Airborne heat: 2B, 8, 8C (Uppercut, then jump cancel into Air Kick)"
local font = Font(help_text);
font.SetSize(22)
font.SetSize(20)
font.SetPos(Vector(10, GetScreenHeight() - 10))
font.SetAlign(WIFALIGN_LEFT, WIFALIGN_BOTTOM)
font.SetColor(0xFF4D21FF)