-
-
Save chrisrbs/21b75824acd678ac93342c6157c87862 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using System.Linq; | |
namespace Bolt.AdvancedTutorial | |
{ | |
public class PlayerController : Bolt.EntityEventListener<IPlayerState> | |
{ | |
const float MOUSE_SENSEITIVITY = 2f; | |
bool forward; | |
bool backward; | |
bool left; | |
bool right; | |
bool jump; | |
bool aiming; | |
bool fire; | |
int weapon; | |
float yaw; | |
float pitch; | |
PlayerMotor _motor; | |
[SerializeField] | |
WeaponBase[] _weapons; | |
[SerializeField] | |
AudioSource _weaponSfxSource; | |
public WeaponBase activeWeapon | |
{ | |
get { return _weapons[state.weapon]; } | |
} | |
void Awake() | |
{ | |
_motor = GetComponent<PlayerMotor>(); | |
} | |
void Update() | |
{ | |
PollKeys(true); | |
if (entity.isOwner && entity.hasControl && Input.GetKey(KeyCode.L)) | |
{ | |
for (int i = 0; i < 100; ++i) | |
{ | |
BoltNetwork.Instantiate(BoltPrefabs.SceneCube, new Vector3(Random.value * 512, Random.value * 512, Random.value * 512), Quaternion.identity); | |
} | |
} | |
} | |
void PollKeys(bool mouse) | |
{ | |
forward = Input.GetKey(KeyCode.W); | |
backward = Input.GetKey(KeyCode.S); | |
left = Input.GetKey(KeyCode.A); | |
right = Input.GetKey(KeyCode.D); | |
jump = Input.GetKey(KeyCode.Space); | |
aiming = Input.GetMouseButton(1); | |
fire = Input.GetMouseButton(0); | |
if (Input.GetKeyDown(KeyCode.Alpha1)) | |
{ | |
weapon = 0; | |
} | |
else if (Input.GetKeyDown(KeyCode.Alpha2)) | |
{ | |
weapon = 1; | |
} | |
if (mouse) | |
{ | |
yaw += (Input.GetAxisRaw("Mouse X") * MOUSE_SENSEITIVITY); | |
yaw %= 360f; | |
pitch += (-Input.GetAxisRaw("Mouse Y") * MOUSE_SENSEITIVITY); | |
pitch = Mathf.Clamp(pitch, -85f, +85f); | |
} | |
} | |
public override void Attached() | |
{ | |
if (entity.isOwner) | |
{ | |
state.tokenTest = new TestToken() { Number = 1337 }; | |
} | |
state.AddCallback("tokenTest", () => | |
{ | |
BoltLog.Info("Received token in .tokenTest property {0}", state.tokenTest); | |
}); | |
state.SetTransforms(state.transform, transform); | |
state.SetAnimator(GetComponentInChildren<Animator>()); | |
// setting layerweights | |
state.Animator.SetLayerWeight(0, 1); | |
state.Animator.SetLayerWeight(1, 1); | |
state.OnFire += OnFire; | |
state.AddCallback("weapon", WeaponChanged); | |
// setup weapon | |
WeaponChanged(); | |
} | |
void WeaponChanged() | |
{ | |
// setup weapon | |
for (int i = 0; i < _weapons.Length; ++i) | |
{ | |
_weapons[i].gameObject.SetActive(false); | |
} | |
_weapons[state.weapon].gameObject.SetActive(true); | |
} | |
void OnFire() | |
{ | |
// play sfx | |
_weaponSfxSource.PlayOneShot(activeWeapon.fireSound); | |
GameUI.instance.crosshair.Spread += 0.1f; | |
// | |
activeWeapon.Fx(entity); | |
} | |
public void ApplyDamage(byte damage) | |
{ | |
if (!state.Dead) | |
{ | |
state.health -= damage; | |
if (state.health > 100 || state.health < 0) | |
{ | |
state.health = 0; | |
} | |
} | |
if (state.health == 0) | |
{ | |
entity.controller.GetPlayer().Kill(); | |
} | |
} | |
public override void SimulateOwner() | |
{ | |
if ((BoltNetwork.Frame % 5) == 0 && (state.Dead == false)) | |
{ | |
state.health = (byte)Mathf.Clamp(state.health + 1, 0, 100); | |
} | |
} | |
public override void SimulateController() | |
{ | |
PollKeys(false); | |
IPlayerCommandInput input = PlayerCommand.Create(); | |
input.forward = forward; | |
input.backward = backward; | |
input.left = left; | |
input.right = right; | |
input.jump = jump; | |
input.aiming = aiming; | |
input.fire = fire; | |
input.yaw = yaw; | |
input.pitch = pitch; | |
input.weapon = weapon; | |
input.Token = new TestToken(); | |
entity.QueueInput(input); | |
} | |
private int verifiedCommandFrame = 0; | |
private bool didStartMoving = false; | |
private int firstFrameWithMovement = 0; | |
private PlayerCommand mostRecentPredictedCommand; | |
public override void ExecuteCommand(Bolt.Command c, bool resetState) | |
{ | |
if (state.Dead) | |
{ | |
return; | |
} | |
PlayerCommand cmd = (PlayerCommand)c; | |
if (resetState) | |
{ | |
if (didStartMoving) { | |
Debug.Log($"{cmd.ServerFrame} The most recent client prediction position is: {mostRecentPredictedCommand?.Result.position}"); | |
} | |
verifiedCommandFrame = cmd.ServerFrame; | |
_motor.SetState(cmd.Result.position, cmd.Result.velocity, cmd.Result.isGrounded, cmd.Result.jumpFrames); | |
} | |
else | |
{ | |
if (cmd.Input.forward && !didStartMoving) { | |
didStartMoving = true; | |
firstFrameWithMovement = cmd.ServerFrame; | |
Debug.Log($"First command with movement: {cmd.ServerFrame}"); | |
} | |
var initialPosition = transform.localPosition; | |
// move and save the resulting state | |
var result = _motor.Move(cmd.Input.forward, cmd.Input.backward, cmd.Input.left, cmd.Input.right, cmd.Input.jump, cmd.Input.yaw); | |
cmd.Result.position = result.position; | |
cmd.Result.velocity = result.velocity; | |
cmd.Result.jumpFrames = result.jumpFrames; | |
cmd.Result.isGrounded = result.isGrounded; | |
if (cmd.IsFirstExecution) | |
{ | |
// animation | |
AnimatePlayer(cmd); | |
// set state pitch | |
state.pitch = cmd.Input.pitch; | |
state.weapon = cmd.Input.weapon; | |
state.Aiming = cmd.Input.aiming; | |
// deal with weapons | |
if (cmd.Input.aiming && cmd.Input.fire) | |
{ | |
FireWeapon(cmd); | |
} | |
} | |
if (entity.isOwner) | |
{ | |
cmd.Result.Token = new TestToken(); | |
} | |
var positionDelta = transform.localPosition - initialPosition; | |
if (didStartMoving && cmd.ServerFrame == verifiedCommandFrame + 1 && positionDelta != Vector3.zero && !cmd.Input.forward) { | |
// This is the first client command to be replayed from the verified command from the server | |
Debug.Log($"Frame: {cmd.ServerFrame} doesn't have movement but still moved from {initialPosition} to {transform.localPosition} which is a delta of {positionDelta}"); | |
} | |
if (mostRecentPredictedCommand == null || cmd.ServerFrame > mostRecentPredictedCommand.ServerFrame) { | |
mostRecentPredictedCommand = cmd; | |
} | |
} | |
} | |
void AnimatePlayer(PlayerCommand cmd) | |
{ | |
// FWD <> BWD movement | |
if (cmd.Input.forward ^ cmd.Input.backward) | |
{ | |
state.MoveZ = cmd.Input.forward ? 1 : -1; | |
} | |
else | |
{ | |
state.MoveZ = 0; | |
} | |
// LEFT <> RIGHT movement | |
if (cmd.Input.left ^ cmd.Input.right) | |
{ | |
state.MoveX = cmd.Input.right ? 1 : -1; | |
} | |
else | |
{ | |
state.MoveX = 0; | |
} | |
// JUMP | |
if (_motor.jumpStartedThisFrame) | |
{ | |
state.Jump(); | |
} | |
} | |
void FireWeapon(PlayerCommand cmd) | |
{ | |
if (activeWeapon.fireFrame + activeWeapon.refireRate <= BoltNetwork.ServerFrame) | |
{ | |
activeWeapon.fireFrame = BoltNetwork.ServerFrame; | |
state.Fire(); | |
// if we are the owner and the active weapon is a hitscan weapon, do logic | |
if (entity.isOwner) | |
{ | |
activeWeapon.OnOwner(cmd, entity); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment