extends Node2D @onready var camera: Camera2D = $Camera2D @onready var countdown: Timer = $countdown #@onready var countdown_label: Label = $hud/countdown_label @onready var cars: Node = $cars @onready var game_hud_animations: AnimationPlayer = $hud/game_hud_animations @onready var times_container: HFlowContainer = $hud/times_container @onready var timer_close: Timer = $timer_close @onready var highscore_label: Label = $hud/highscore_label @onready var splittime_label: Label = $hud/splittime_label const caroffset= 32+4 #space cars on start line const caroffset_side= 16 const caroffset_rear= 32+16 var viewCarMargin=Vector2(0.7,0.7) #1=zoom out full speed when car is at border. 0.9 zoom out full speed when car is 10% away from border var viewCarMargin_zoomstart=viewCarMargin-Vector2(0.2,0.2) #start zooming var viewCarMargin_zoombackup=viewCarMargin_zoomstart-Vector2(0.1,0.1) #start zooming back in var zoomspeed=0.5 var zoomspeed_backup=0.1 #relative to screen size var zoom_normal=1.5 const CAMERA_POSITION_SPEED=0.02 #0.0 - 1.0, higher=faster var camera_zoom_out_lookahead_time=1 #predicted position of car in t seconds for camera coverage. greater value=earlier zoom out and movement of camera var running=false var highscorecheckpointtimes=null var time_close_keypressed=1 #seconds to wait after key pressed at endscreen var game_ended=false # Called when the node enters the scene tree for the first time. func _ready() -> void: Gamestate.resetTimeElapsed() #countdown.start() game_hud_animations.play("game_start") #countdown_label.visible=true #Load Map var checkpoints :Array[String]=[] var mapscene=load(Gamestate.getSelectedMapSceneName()) var mapsceneinstance=mapscene.instantiate() add_child(mapsceneinstance) for mapc in mapsceneinstance.get_children(): if mapc.name.begins_with("area_cp"): checkpoints.append(mapc.name) print(str(checkpoints.size())+" Checkpoints found") var i=0 var players_ranked :Array = Gamestate.getPlayers().duplicate() players_ranked.sort_custom(custom_array_sort_rank) #arrange players by handicap/rank for player in Gamestate.getPlayers(): #create all players #get player rank var i_position=0 while i_position<players_ranked.size() and player.inputkey!=players_ranked[i_position].inputkey: i_position+=1 var newcarscene=load("res://scenes/car.tscn") var newcarinstance=newcarscene.instantiate() cars.add_child(newcarinstance) newcarinstance.setPlayerinformation(i,player.color,player) #newcarinstance.setPosition(Vector2(0,ceil(i/2.0)*(fmod(i,2)-0.5)*2.0*caroffset)) #all in a row, alternating up/down newcarinstance.setPosition(Vector2(caroffset_rear*-1*i_position,(fmod(i_position,2)-0.5)*2.0*caroffset_side)) newcarinstance.setCheckpoints(checkpoints,Gamestate.getRounds()) newcarinstance.getCharacterBody().car_finished.connect(_on_car_finished) newcarinstance.getCharacterBody().car_on_checkpoint.connect(_on_car_on_checkpoint) var startmarker:Sprite2D=mapsceneinstance.get_node("startmarker") if startmarker != null: var newmarker:Sprite2D=startmarker.duplicate() newmarker.position=newcarinstance.getPosition() mapsceneinstance.add_child(newmarker) print("added startmarker pos="+str(newmarker.position)) startmarker.queue_free() #remove template marker print("i_position="+str(i_position)+" carpos is ="+str(newcarinstance.getPosition())+" instancepos="+str(newcarinstance.position)+" colori="+str(player.colori)+" color="+str(player.color)) i+=1 highscorecheckpointtimes=HighscoreHandler.loadHighcoreCheckpoints(Gamestate.getSelectedMapName(),"normal",str(Gamestate.getRounds())) func custom_array_sort_rank(a, b): return a.rank > b.rank #a>b = higher rank (more wins) -> better start position # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: if running: Gamestate.addTimeElapsed(delta) #if !countdown.is_stopped(): # countdown_label.text=str(round(countdown.time_left)) updateCameraMovement(delta) #$hud/debuglabel.text=""+str(calculatedViewCarMargin)+" / "+str(viewCarMargin_zoomstart)+" zoomspeed="+str(mapped_zoomspeed) #$hud/debuglabel.text=""+str(calculatedViewCarMargin)+" / "+str(viewsize)+" zoomspeed="+str(mapped_zoomspeed) $hud/timer.text=str(round(Gamestate.getTimeElapsed()*1000)/1000.0) if game_ended: var anyplayerkeypressed=false var id=0 for p in Gamestate.getPlayers(): if Input.is_action_pressed(Gamestate.userinput_prefix+str(id)): anyplayerkeypressed=true id+=1 if anyplayerkeypressed and timer_close.time_left<time_close_keypressed: timer_close.wait_time=time_close_keypressed #set time back timer_close.stop() #prepare for restarting at this time if !anyplayerkeypressed and timer_close.is_stopped(): timer_close.start() #start timer when no key is pressed func updateCameraMovement(delta: float): var cars=cars.get_children() var displayedCarCount=0 var maxCarSpeed=0 var minPos=Vector2.ZERO #min/max x and y position of all cars var maxPos=Vector2.ZERO var oneDriving=false for c in cars: #check if any one car is still driving if !c.hasFinished(): oneDriving=true for c in cars: if !c.hasFinished() or !oneDriving: var carpos = c.getPosition() var carposLookahead= c.getPositionLookahead(camera_zoom_out_lookahead_time) $hud/debuglabel.text=""+str(carpos)+"\n"+str(carposLookahead) maxCarSpeed=max(maxCarSpeed,c.getSpeed()) if displayedCarCount==0: minPos=carpos.min(carposLookahead) maxPos=carpos.max(carposLookahead) else: minPos=minPos.min(carpos).min(carposLookahead) maxPos=maxPos.max(carpos).max(carposLookahead) displayedCarCount+=1 camera.position=lerp(camera.position,(minPos+maxPos)/2.0,CAMERA_POSITION_SPEED) var viewsize = camera.get_viewport_rect().size var carSpread=Vector2(maxPos.x-minPos.x,maxPos.y-minPos.y) var calculatedViewCarMargin=carSpread*camera.zoom #when cars are screen width apart this number matches viewsize=camera.get_viewport_rect().size var viewCarMargin_zoomstart_pixels=viewCarMargin_zoomstart*viewsize/camera.zoom var viewCarMargin_pixels=viewCarMargin*viewsize/camera.zoom var viewCarMargin_zoombackup_pixels=viewCarMargin_zoombackup*viewsize/camera.zoom var mapped_zoomspeed_x=constrain(remap(calculatedViewCarMargin.x , viewCarMargin_zoomstart_pixels.x,viewCarMargin_pixels.x,0,zoomspeed),0,zoomspeed) var mapped_zoomspeed_y=constrain(remap(calculatedViewCarMargin.y , viewCarMargin_zoomstart_pixels.y,viewCarMargin_pixels.y,0,zoomspeed),0,zoomspeed) var mapped_zoomspeed=max(mapped_zoomspeed_x,mapped_zoomspeed_y) if calculatedViewCarMargin.x>viewCarMargin_zoomstart_pixels.x or calculatedViewCarMargin.y>viewCarMargin_zoomstart_pixels.y: #cars not in view camera.zoom-=Vector2(mapped_zoomspeed*delta,mapped_zoomspeed*delta) elif calculatedViewCarMargin.x<viewCarMargin_zoombackup_pixels.x and calculatedViewCarMargin.y<viewCarMargin_zoombackup_pixels.y: #cars in view again if camera.zoom.x<zoom_normal: camera.zoom+=Vector2(zoomspeed_backup*delta,zoomspeed_backup*delta) func _input(ev): #if ev is InputEventKey and if Input.is_action_just_pressed("ui_cancel"): get_tree().change_scene_to_file("res://scenes/menu.tscn") func checkPositionInsideView(checkpos: Vector2) -> bool: var viewsize = camera.get_viewport_rect().size var canvas_pos = camera.get_viewport().get_canvas_transform().affine_inverse() * -checkpos print("cp="+str(canvas_pos) + " viewsize="+str(viewsize)) if canvas_pos.x < 0 and canvas_pos.x > -viewsize.x: if canvas_pos.y < 0 and canvas_pos.y > -viewsize.y: return true return false func constrain(val,a,b): var vmin=min(a,b) var vmax=max(a,b) return min(vmax,max(vmin,val)) func constrain2(val:Vector2,a:Vector2,b:Vector2): var vmin=a.min(b) var vmax=a.max(b) return vmax.min(vmin.max(val)) func _on_countdown_timeout() -> void: running=true var cars=cars.get_children() for c in cars: c.setRunning(true) #countdown_label.visible=false func _on_car_finished(playerid,finalTime) -> void: times_container.addFinishedPlayer(playerid,finalTime,Gamestate.getPlayers().size()) if times_container.getPlayersFinished() == Gamestate.getPlayers().size(): #all players have finish times finishGame()# Game finished func _on_car_on_checkpoint(playerid,checkpointtimes,i) -> void: if Gamestate.getPlayers().size()==1 and highscorecheckpointtimes != null: #singleplayer and previous split times exista # calculate split times var timediff = checkpointtimes[i]-highscorecheckpointtimes[i] timediff=round(timediff*1000)/1000 #splittime_label.visible=true game_hud_animations.play("show_splittime") if timediff==0: #same time or first entry splittime_label.text=="+"+str(timediff) splittime_label.self_modulate=Color(200,200,0) elif timediff<0: #new highscore splittime_label.text=str(timediff) splittime_label.self_modulate=Color(0,0,200) elif timediff>0: #worse time splittime_label.text="+"+str(timediff) splittime_label.self_modulate=Color(200,0,0) func finishGame(): print("Game Finished") running=false game_ended=true #change rank for players var players_ranked :Array = Gamestate.getPlayers().duplicate() players_ranked.sort_custom(custom_array_sort_player_finaltime) #arrange players by handicap/rank var i=0 for p in players_ranked: print("Player "+str(i)+" key="+str(OS.get_keycode_string(p.inputkey))+" was rank "+str(p.rank)) var floatrank=1.0-(i*1.0/(Gamestate.getPlayers().size()-1)) #winning player=1.0, losing player=0.0 var rankchangedown=-0.2 #change rank down limit var rankchangeup=0.5 #change rank up limit p.rank=p.rank+min(max((floatrank-p.rank),rankchangedown),rankchangeup) print(" changed to rank "+str(p.rank)) i+=1 if Gamestate.getPlayers().size()==1: #was played in singleplayer var timediff=HighscoreHandler.updateHighscore(Gamestate.getSelectedMapName(),"normal",str(Gamestate.getRounds()),getfinalTimeByPlayer(Gamestate.getPlayers()[0]),getcheckpointtimesByPlayer(Gamestate.getPlayers()[0])) print("Timediff="+str(timediff)) timediff=round(timediff*1000)/1000 highscore_label.visible=true if timediff==0: #same time or first entry highscore_label.text=="+"+str(timediff) highscore_label.modulate=Color(200,200,0) elif timediff<0: #new highscore highscore_label.text=str(timediff) highscore_label.modulate=Color(0,0,200) elif timediff>0: #worse time highscore_label.text="+"+str(timediff) highscore_label.modulate=Color(200,0,0) game_hud_animations.play("game_end") timer_close.start() #overlaycolor.visible=true func custom_array_sort_player_finaltime(a, b): #var cars=cars.get_children() var finalTimeA=getfinalTimeByPlayer(a) var finalTimeB=getfinalTimeByPlayer(b) if finalTimeA != null and finalTimeB != null: return finalTimeA < finalTimeB else: return false #for c in cars: # if c.reference_gamestateplayer==a: #finalTimeA=c.getCharacterBody().finalTime #elif c.reference_gamestateplayer==b: #finalTimeB=c.getCharacterBody().finalTime func getPlayerCB(player): var cars=cars.get_children() for c in cars: if c.reference_gamestateplayer==player: return c.getCharacterBody() return null func getfinalTimeByPlayer(player): var p=getPlayerCB(player) if p != null: return p.finalTime return null func getcheckpointtimesByPlayer(player): var p=getPlayerCB(player) if p != null: return p.checkpointtimes return null func _on_timer_close_timeout() -> void: print("Close Map") get_tree().change_scene_to_file("res://scenes/menu.tscn")